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,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Scryglass
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Config
         
     | 
| 
      
 5 
     | 
    
         
            +
                using ClipStringRefinement
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                attr_accessor :tab_length
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :include_empty_associations,
         
     | 
| 
      
 9 
     | 
    
         
            +
                              :include_through_associations, :include_scoped_associations,
         
     | 
| 
      
 10 
     | 
    
         
            +
                              :show_association_types
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_accessor :cursor_tracking
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_accessor :lenses
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_accessor :tree_view_key_string_clip_length,
         
     | 
| 
      
 14 
     | 
    
         
            +
                              :tree_view_value_string_clip_length
         
     | 
| 
      
 15 
     | 
    
         
            +
                attr_accessor :dot_coloring
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ## Display
         
     | 
| 
      
 19 
     | 
    
         
            +
                  self.tab_length = 2 # You can make it 0, but please don't make it 0.
         
     | 
| 
      
 20 
     | 
    
         
            +
                  self.tree_view_key_string_clip_length = 200
         
     | 
| 
      
 21 
     | 
    
         
            +
                  self.tree_view_value_string_clip_length = 500
         
     | 
| 
      
 22 
     | 
    
         
            +
                  self.dot_coloring = true
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  ## UX
         
     | 
| 
      
 25 
     | 
    
         
            +
                  self.cursor_tracking = [:flexible_range, :dead_center][0] # One or the other
         
     | 
| 
      
 26 
     | 
    
         
            +
                  self.lenses = [ # Custom lenses can easily be added as name+lambda hashes! Or comment some out to turn them off.
         
     | 
| 
      
 27 
     | 
    
         
            +
                    { name: 'Pretty Print (`pp`)',
         
     | 
| 
      
 28 
     | 
    
         
            +
                      lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { pp o } } },
         
     | 
| 
      
 29 
     | 
    
         
            +
                    { name: 'Inspect (`.inspect`)',
         
     | 
| 
      
 30 
     | 
    
         
            +
                      lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o.inspect } } },
         
     | 
| 
      
 31 
     | 
    
         
            +
                    { name: 'Yaml Print (`y`)',
         
     | 
| 
      
 32 
     | 
    
         
            +
                      lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { require 'yaml' ; y o } } }, # OR: `puts o.to_yaml`
         
     | 
| 
      
 33 
     | 
    
         
            +
                    { name: 'Puts (`puts`)',
         
     | 
| 
      
 34 
     | 
    
         
            +
                      lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o } } },
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # { name: 'Method Showcase',
         
     | 
| 
      
 36 
     | 
    
         
            +
                    #   lambda: ->(o) { Scryglass::LensHelper.method_showcase_for(o) } },
         
     | 
| 
      
 37 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  ## Building ActiveRecord association sub-rows:
         
     | 
| 
      
 40 
     | 
    
         
            +
                  self.include_empty_associations = true
         
     | 
| 
      
 41 
     | 
    
         
            +
                  self.include_through_associations = false
         
     | 
| 
      
 42 
     | 
    
         
            +
                  self.include_scoped_associations = false
         
     | 
| 
      
 43 
     | 
    
         
            +
                  self.show_association_types = true
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def validate!
         
     | 
| 
      
 47 
     | 
    
         
            +
                  validate_boolean_attrs!
         
     | 
| 
      
 48 
     | 
    
         
            +
                  validate_positive_integer_attrs!
         
     | 
| 
      
 49 
     | 
    
         
            +
                  validate_cursor_tracking_options!
         
     | 
| 
      
 50 
     | 
    
         
            +
                  validate_lenses!
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                private
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def validate_boolean_attrs!
         
     | 
| 
      
 56 
     | 
    
         
            +
                  bool_attrs = [
         
     | 
| 
      
 57 
     | 
    
         
            +
                    :include_empty_associations,
         
     | 
| 
      
 58 
     | 
    
         
            +
                    :include_through_associations,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    :include_scoped_associations,
         
     | 
| 
      
 60 
     | 
    
         
            +
                    :dot_coloring,
         
     | 
| 
      
 61 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 62 
     | 
    
         
            +
                  bool_attrs.each do |bool_attr|
         
     | 
| 
      
 63 
     | 
    
         
            +
                    value = send(bool_attr)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    unless [true, false].include?(value)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      raise ArgumentError, "#{bool_attr} must be true or false."
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def validate_positive_integer_attrs!
         
     | 
| 
      
 71 
     | 
    
         
            +
                  positive_integer_attrs = [
         
     | 
| 
      
 72 
     | 
    
         
            +
                    :tab_length,
         
     | 
| 
      
 73 
     | 
    
         
            +
                    :tree_view_key_string_clip_length,
         
     | 
| 
      
 74 
     | 
    
         
            +
                    :tree_view_value_string_clip_length,
         
     | 
| 
      
 75 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 76 
     | 
    
         
            +
                  positive_integer_attrs.each do |int_attr|
         
     | 
| 
      
 77 
     | 
    
         
            +
                    value = send(int_attr)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    unless value.integer? && value.positive?
         
     | 
| 
      
 79 
     | 
    
         
            +
                      raise ArgumentError, "#{value} is not a positive integer."
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def validate_cursor_tracking_options!
         
     | 
| 
      
 85 
     | 
    
         
            +
                  cursor_tracking_options = [:flexible_range, :dead_center]
         
     | 
| 
      
 86 
     | 
    
         
            +
                  unless cursor_tracking_options.include?(cursor_tracking)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    raise ArgumentError, "#{cursor_tracking.inspect} not in " \
         
     | 
| 
      
 88 
     | 
    
         
            +
                                         "[#{cursor_tracking_options.map(&:inspect).join(', ')}]."
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def validate_lenses!
         
     | 
| 
      
 93 
     | 
    
         
            +
                  raise ArgumentError, 'lenses cannot be empty' unless lenses.any?
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  lenses.each do |lens|
         
     | 
| 
      
 96 
     | 
    
         
            +
                    unless lens.is_a?(Hash) && lens[:name].is_a?(String) && lens[:lambda].lambda?
         
     | 
| 
      
 97 
     | 
    
         
            +
                      raise ArgumentError, "Lens #{lens.inspect} must be a hash of the form:" \
         
     | 
| 
      
 98 
     | 
    
         
            +
                                           '{ name: String, lambda: lambda }'
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Scryglass
         
     | 
| 
      
 4 
     | 
    
         
            +
              module LensHelper
         
     | 
| 
      
 5 
     | 
    
         
            +
                def method_showcase_for(object)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  method_list = object.methods - Object.methods
         
     | 
| 
      
 7 
     | 
    
         
            +
                  label_space = [method_list.map(&:length).max, 45].min
         
     | 
| 
      
 8 
     | 
    
         
            +
                  method_list.sort.map do |method_name|
         
     | 
| 
      
 9 
     | 
    
         
            +
                    label = method_name.to_s.ljust(label_space, ' ')
         
     | 
| 
      
 10 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 11 
     | 
    
         
            +
                      method = object.method(method_name)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      label + '  :  ' +
         
     | 
| 
      
 13 
     | 
    
         
            +
                        method.source_location.to_a.join(':') + "\n" +
         
     | 
| 
      
 14 
     | 
    
         
            +
                        Hexes.capture_io { puts method.source }
         
     | 
| 
      
 15 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 16 
     | 
    
         
            +
                      label + '  :  Error: ' +
         
     | 
| 
      
 17 
     | 
    
         
            +
                        e.message + "\n"
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end.join("\n")
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,140 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Scryglass
         
     | 
| 
      
 4 
     | 
    
         
            +
              class LensPanel < Scryglass::ViewPanel
         
     | 
| 
      
 5 
     | 
    
         
            +
                using ClipStringRefinement
         
     | 
| 
      
 6 
     | 
    
         
            +
                using AnsilessStringRefinement
         
     | 
| 
      
 7 
     | 
    
         
            +
                using ArrayFitToRefinement
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                private
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def self.lenses
         
     | 
| 
      
 12 
     | 
    
         
            +
                  Scryglass.config.lenses
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def uncut_body_string
         
     | 
| 
      
 16 
     | 
    
         
            +
                  current_lens         = scry_session.current_lens
         
     | 
| 
      
 17 
     | 
    
         
            +
                  current_subject_type = scry_session.current_subject_type
         
     | 
| 
      
 18 
     | 
    
         
            +
                  current_subject      = scry_session.current_ro.current_subject
         
     | 
| 
      
 19 
     | 
    
         
            +
                  ro_has_no_key        = !scry_session.current_ro.key_value_pair?
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  return '' if ro_has_no_key && current_subject_type == :key
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  lens_id = current_lens % LensPanel.lenses.count
         
     | 
| 
      
 24 
     | 
    
         
            +
                  lens = LensPanel.lenses[lens_id]
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  scry_session.current_ro.lens_strings[current_subject_type][lens_id] ||=
         
     | 
| 
      
 27 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 28 
     | 
    
         
            +
                      lens[:lambda].call(current_subject)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 30 
     | 
    
         
            +
                      [e.message, *e.backtrace].join("\n")
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def uncut_header_string
         
     | 
| 
      
 35 
     | 
    
         
            +
                  _screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 36 
     | 
    
         
            +
                  dotted_line = '·' * screen_width
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  [
         
     | 
| 
      
 39 
     | 
    
         
            +
                    current_ro_subheader,
         
     | 
| 
      
 40 
     | 
    
         
            +
                    dotted_line,
         
     | 
| 
      
 41 
     | 
    
         
            +
                    lens_param_subheader,
         
     | 
| 
      
 42 
     | 
    
         
            +
                    dotted_line
         
     | 
| 
      
 43 
     | 
    
         
            +
                  ].join("\n")
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def visible_body_slice(uncut_body_string)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 48 
     | 
    
         
            +
                  non_header_view_size = screen_height - visible_header_string.split("\n").count
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  split_lines = uncut_body_string.split("\n")
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  ## Here we cut down the (rectangular) display array in both dimensions (into a smaller rectangle), as needed, to fit the view.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  sliced_lines = split_lines.map do |string|
         
     | 
| 
      
 54 
     | 
    
         
            +
                    ansi_length = string.length - string.ansiless_length # Escape codes make `length` different from display length!
         
     | 
| 
      
 55 
     | 
    
         
            +
                    slice_length = screen_width + ansi_length
         
     | 
| 
      
 56 
     | 
    
         
            +
                    string[current_view_coords[:x], slice_length] || '' # If I don't want to
         
     | 
| 
      
 57 
     | 
    
         
            +
                    #   opacify here, I need to account for nils when the view is fully
         
     | 
| 
      
 58 
     | 
    
         
            +
                    #   beyond the shorter lines.
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  sliced_list = sliced_lines[current_view_coords[:y], non_header_view_size]
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  sliced_list.join("\n")
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def recalculate_y_boundaries
         
     | 
| 
      
 66 
     | 
    
         
            +
                  self.y_boundaries = 0...(uncut_body_string.count("\n") + 1)
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def recalculate_x_boundaries
         
     | 
| 
      
 70 
     | 
    
         
            +
                  _screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  split_lines = uncut_body_string.split("\n")
         
     | 
| 
      
 73 
     | 
    
         
            +
                  length_of_longest_line = split_lines.map(&:length).max || 0
         
     | 
| 
      
 74 
     | 
    
         
            +
                  max_line_length = [length_of_longest_line, screen_width].max
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  self.x_boundaries = 0...max_line_length
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def current_ro_subheader
         
     | 
| 
      
 80 
     | 
    
         
            +
                  current_ro = scry_session.current_ro
         
     | 
| 
      
 81 
     | 
    
         
            +
                  user_input = scry_session.user_input
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  row_above_string =
         
     | 
| 
      
 84 
     | 
    
         
            +
                    current_ro.next_visible_ro_up.to_s if current_ro.next_visible_ro_up
         
     | 
| 
      
 85 
     | 
    
         
            +
                  row_below_string =
         
     | 
| 
      
 86 
     | 
    
         
            +
                    current_ro.next_visible_ro_down.to_s if current_ro.next_visible_ro_down
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  tree_preview_related_commands = ['A', 'B', 'C', 'D',
         
     | 
| 
      
 89 
     | 
    
         
            +
                                                   '@', '.', '(', '*', '|', '-']
         
     | 
| 
      
 90 
     | 
    
         
            +
                  ro_view_label =
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if tree_preview_related_commands.include?(user_input)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      "\e[7mVIEWING:\e[00m" # Color reversed
         
     | 
| 
      
 93 
     | 
    
         
            +
                    else
         
     | 
| 
      
 94 
     | 
    
         
            +
                      'VIEWING:'
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  current_ro_window =
         
     | 
| 
      
 98 
     | 
    
         
            +
                            "          #{row_above_string}\n" \
         
     | 
| 
      
 99 
     | 
    
         
            +
                    "#{ro_view_label}  #{current_ro}\n" \
         
     | 
| 
      
 100 
     | 
    
         
            +
                            "          #{row_below_string}"
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  current_ro_window
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                def lens_param_subheader
         
     | 
| 
      
 106 
     | 
    
         
            +
                  _screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  current_lens         = scry_session.current_lens
         
     | 
| 
      
 109 
     | 
    
         
            +
                  current_subject_type = scry_session.current_subject_type
         
     | 
| 
      
 110 
     | 
    
         
            +
                  current_subject      = scry_session.current_ro.current_subject
         
     | 
| 
      
 111 
     | 
    
         
            +
                  user_input           = scry_session.user_input
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  lens_count = LensPanel.lenses.count
         
     | 
| 
      
 114 
     | 
    
         
            +
                  lens_id    = current_lens % lens_count
         
     | 
| 
      
 115 
     | 
    
         
            +
                  lens       = LensPanel.lenses[lens_id]
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  longest_lens_name_length = LensPanel.lenses.map do |lens|
         
     | 
| 
      
 118 
     | 
    
         
            +
                                               lens[:name].length
         
     | 
| 
      
 119 
     | 
    
         
            +
                                             end.max
         
     | 
| 
      
 120 
     | 
    
         
            +
                  lens_type_header_length = 9 + (lens_count.to_s.length * 2)
         
     | 
| 
      
 121 
     | 
    
         
            +
                                              + longest_lens_name_length
         
     | 
| 
      
 122 
     | 
    
         
            +
                  subject_type_header  = "SUBJECT: #{current_subject_type}".ljust(14, ' ')
         
     | 
| 
      
 123 
     | 
    
         
            +
                  subject_class_header = "   CLASS: #{current_subject.class}"
         
     | 
| 
      
 124 
     | 
    
         
            +
                  lens_type_header     = " LENS #{lens_id + 1}/#{lens_count}: #{lens[:name]}"
         
     | 
| 
      
 125 
     | 
    
         
            +
                                         .ljust(lens_type_header_length, ' ')
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  fit_lens_header = [
         
     | 
| 
      
 128 
     | 
    
         
            +
                    subject_type_header, subject_class_header, lens_type_header
         
     | 
| 
      
 129 
     | 
    
         
            +
                  ].fit_to(screen_width)
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  if user_input == 'l'
         
     | 
| 
      
 132 
     | 
    
         
            +
                    fit_lens_header[4] = "\e[7m#{fit_lens_header[4]}" # Format to be ended by Hexes.opacify_screen_string() (using \e[00m)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  elsif user_input == 'L'
         
     | 
| 
      
 134 
     | 
    
         
            +
                    fit_lens_header[0] = "\e[7m#{fit_lens_header[0]}\e[00m"
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  fit_lens_header.join('')
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
              end
         
     | 
| 
      
 140 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/scryglass/ro.rb
    ADDED
    
    | 
         @@ -0,0 +1,237 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Scryglass
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A ro is essentially a complex wrapper for an object that deals with how it is
         
     | 
| 
      
 5 
     | 
    
         
            +
              #   nested and displayed relative to other ros/objects.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Ro
         
     | 
| 
      
 7 
     | 
    
         
            +
                using ClipStringRefinement
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :key, :value, :val_type,
         
     | 
| 
      
 10 
     | 
    
         
            +
                              :key_string, :value_string, :lens_strings,
         
     | 
| 
      
 11 
     | 
    
         
            +
                              :key_value_relationship_indicator, :special_sub_ro_type,
         
     | 
| 
      
 12 
     | 
    
         
            +
                              :wrappers
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :has_cursor, :expanded,
         
     | 
| 
      
 15 
     | 
    
         
            +
                              :parent_ro, :sub_ros,
         
     | 
| 
      
 16 
     | 
    
         
            +
                              :depth, :index, :scry_session
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                WRAPPER_TYPES = {
         
     | 
| 
      
 19 
     | 
    
         
            +
                  'Hash' => '{}',
         
     | 
| 
      
 20 
     | 
    
         
            +
                  'Array' => '[]',
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'ActiveRecord_Relation' => '<>',
         
     | 
| 
      
 22 
     | 
    
         
            +
                  'ActiveRecord_Associations_CollectionProxy' => '‹›',
         
     | 
| 
      
 23 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def initialize(scry_session:,
         
     | 
| 
      
 26 
     | 
    
         
            +
                               val:,
         
     | 
| 
      
 27 
     | 
    
         
            +
                               val_type:,
         
     | 
| 
      
 28 
     | 
    
         
            +
                               parent_ro:,
         
     | 
| 
      
 29 
     | 
    
         
            +
                               key:,
         
     | 
| 
      
 30 
     | 
    
         
            +
                               depth:,
         
     | 
| 
      
 31 
     | 
    
         
            +
                               key_value_relationship_indicator: false,
         
     | 
| 
      
 32 
     | 
    
         
            +
                               special_sub_ro_type: nil)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  key_clip_length = Scryglass.config.tree_view_key_string_clip_length
         
     | 
| 
      
 34 
     | 
    
         
            +
                  value_clip_length = Scryglass.config.tree_view_value_string_clip_length
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  self.has_cursor = false
         
     | 
| 
      
 37 
     | 
    
         
            +
                  self.expanded = false
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  self.key_value_relationship_indicator = key_value_relationship_indicator
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  ## Open up ViewWrappers and grab their objects and their custom strings
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if key.class == Scryglass::ViewWrapper
         
     | 
| 
      
 43 
     | 
    
         
            +
                    self.key_string = key.to_s.clip_at(key_clip_length)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    self.key = key.model
         
     | 
| 
      
 45 
     | 
    
         
            +
                  else
         
     | 
| 
      
 46 
     | 
    
         
            +
                    self.key_string = key.inspect.clip_at(key_clip_length)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    self.key = key
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  if val.class == Scryglass::ViewWrapper
         
     | 
| 
      
 50 
     | 
    
         
            +
                    self.value_string = val.to_s.clip_at(value_clip_length)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    self.value = val.model
         
     | 
| 
      
 52 
     | 
    
         
            +
                  else
         
     | 
| 
      
 53 
     | 
    
         
            +
                    self.value_string = val.inspect.clip_at(value_clip_length)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    self.value = val
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  self.sub_ros = []
         
     | 
| 
      
 58 
     | 
    
         
            +
                  self.parent_ro = parent_ro
         
     | 
| 
      
 59 
     | 
    
         
            +
                  self.val_type = val_type
         
     | 
| 
      
 60 
     | 
    
         
            +
                  self.special_sub_ro_type = special_sub_ro_type
         
     | 
| 
      
 61 
     | 
    
         
            +
                  self.depth = depth
         
     | 
| 
      
 62 
     | 
    
         
            +
                  self.wrappers = WRAPPER_TYPES[value.class.to_s.split('::').last] || '?¿'
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  self.lens_strings = { key: {}, value: {} }
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  self.index = scry_session.all_ros.count
         
     | 
| 
      
 67 
     | 
    
         
            +
                  scry_session.all_ros << self
         
     | 
| 
      
 68 
     | 
    
         
            +
                  self.scry_session = scry_session
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def top_ro?
         
     | 
| 
      
 72 
     | 
    
         
            +
                  parent_ro.nil?
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 76 
     | 
    
         
            +
                  value_indicator =
         
     | 
| 
      
 77 
     | 
    
         
            +
                    bucket? ? bucket_indicator : value_string
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  key_value_spacer =
         
     | 
| 
      
 80 
     | 
    
         
            +
                    key_value_pair? ? key_string + key_value_relationship_indicator : ''
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  special_sub_ro_expansion_indicator =
         
     | 
| 
      
 83 
     | 
    
         
            +
                    special_sub_ros.any? && !expanded ? '•' : ' '
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  left_fill_string + special_sub_ro_expansion_indicator +
         
     | 
| 
      
 86 
     | 
    
         
            +
                    key_value_spacer + value_indicator
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def next_visible_ro_down
         
     | 
| 
      
 90 
     | 
    
         
            +
                  subsequent_ros = scry_session.all_ros[(index + 1)..-1]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  subsequent_ros.find(&:visible?)
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                def next_visible_ro_up
         
     | 
| 
      
 95 
     | 
    
         
            +
                  preceding_ros = scry_session.all_ros[0...index]
         
     | 
| 
      
 96 
     | 
    
         
            +
                  preceding_ros.reverse.find(&:visible?)
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                def current_subject
         
     | 
| 
      
 100 
     | 
    
         
            +
                  send(scry_session.current_subject_type)
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def iv_sub_ros
         
     | 
| 
      
 104 
     | 
    
         
            +
                  sub_ros.select { |sub_ro| sub_ro.special_sub_ro_type == :iv }
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                def ar_sub_ros
         
     | 
| 
      
 108 
     | 
    
         
            +
                  sub_ros.select { |sub_ro| sub_ro.special_sub_ro_type == :ar }
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                def enum_sub_ros
         
     | 
| 
      
 112 
     | 
    
         
            +
                  sub_ros.select { |sub_ro| sub_ro.special_sub_ro_type == :enum }
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                # (Used for recalculate_indeces after new Ros have been injected)
         
     | 
| 
      
 116 
     | 
    
         
            +
                def next_ro_without_using_index
         
     | 
| 
      
 117 
     | 
    
         
            +
                  if sub_ros.any?
         
     | 
| 
      
 118 
     | 
    
         
            +
                    sub_ros.first
         
     | 
| 
      
 119 
     | 
    
         
            +
                  elsif top_ro?
         
     | 
| 
      
 120 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 121 
     | 
    
         
            +
                  elsif sibling_down.present?
         
     | 
| 
      
 122 
     | 
    
         
            +
                    sibling_down
         
     | 
| 
      
 123 
     | 
    
         
            +
                  else
         
     | 
| 
      
 124 
     | 
    
         
            +
                    upward_feeler_ro = self
         
     | 
| 
      
 125 
     | 
    
         
            +
                    until upward_feeler_ro.sibling_down.present? || upward_feeler_ro.top_ro?
         
     | 
| 
      
 126 
     | 
    
         
            +
                      upward_feeler_ro = upward_feeler_ro.parent_ro
         
     | 
| 
      
 127 
     | 
    
         
            +
                    end
         
     | 
| 
      
 128 
     | 
    
         
            +
                    upward_feeler_ro.sibling_down
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def sibling_down
         
     | 
| 
      
 133 
     | 
    
         
            +
                  return nil if top_ro?
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  siblings = parent_ro.sub_ros
         
     | 
| 
      
 136 
     | 
    
         
            +
                  self_index = siblings.index(self)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  return nil if self == siblings.last
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  siblings[self_index + 1]
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                ## This exists so that an easy *unordered array match* can occur elsewhere.
         
     | 
| 
      
 143 
     | 
    
         
            +
                def <=>(other)
         
     | 
| 
      
 144 
     | 
    
         
            +
                  unless self.class == other.class
         
     | 
| 
      
 145 
     | 
    
         
            +
                    raise ArgumentError, "Comparison of #{self.class} with #{other.class}"
         
     | 
| 
      
 146 
     | 
    
         
            +
                  end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                  object_id <=> other.object_id
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                def visible?
         
     | 
| 
      
 153 
     | 
    
         
            +
                  return true if top_ro?
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  scanning_ro = parent_ro
         
     | 
| 
      
 156 
     | 
    
         
            +
                  until scanning_ro.top_ro? || !scanning_ro.expanded
         
     | 
| 
      
 157 
     | 
    
         
            +
                    scanning_ro = scanning_ro.parent_ro
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                  scanning_ro.expanded
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def bucket?
         
     | 
| 
      
 164 
     | 
    
         
            +
                  val_type == :bucket
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def nugget?
         
     | 
| 
      
 168 
     | 
    
         
            +
                  val_type == :nugget
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                def key_value_pair?
         
     | 
| 
      
 172 
     | 
    
         
            +
                  !!key_value_relationship_indicator
         
     | 
| 
      
 173 
     | 
    
         
            +
                end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                private
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                def special_sub_ros
         
     | 
| 
      
 178 
     | 
    
         
            +
                  sub_ros.select(&:special_sub_ro_type)
         
     | 
| 
      
 179 
     | 
    
         
            +
                end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                def normal_sub_ros
         
     | 
| 
      
 182 
     | 
    
         
            +
                  sub_ros.reject(&:special_sub_ro_type)
         
     | 
| 
      
 183 
     | 
    
         
            +
                end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                def bucket_indicator
         
     | 
| 
      
 186 
     | 
    
         
            +
                  if expanded && normal_sub_ros.any?
         
     | 
| 
      
 187 
     | 
    
         
            +
                    wrappers[0]
         
     | 
| 
      
 188 
     | 
    
         
            +
                  elsif normal_sub_ros.any?
         
     | 
| 
      
 189 
     | 
    
         
            +
                    # Number of dots indicating order of magnitude for Enumerable's count:
         
     | 
| 
      
 190 
     | 
    
         
            +
                    #   Turning this off (the consistent three dots is more like an ellipsis,
         
     | 
| 
      
 191 
     | 
    
         
            +
                    #   communicating with a solid preexisting symbol), but keeping the idea here:
         
     | 
| 
      
 192 
     | 
    
         
            +
                    #     sub_ros_order_of_magnitude = normal_sub_ros.count.to_s.length
         
     | 
| 
      
 193 
     | 
    
         
            +
                    #     wrappers.dup.insert(1, '•' * sub_ros_order_of_magnitude)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    wrappers.dup.insert(1, '•••')
         
     | 
| 
      
 195 
     | 
    
         
            +
                  else
         
     | 
| 
      
 196 
     | 
    
         
            +
                    wrappers
         
     | 
| 
      
 197 
     | 
    
         
            +
                  end
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                def left_fill_string
         
     | 
| 
      
 201 
     | 
    
         
            +
                  left_fill = if has_cursor
         
     | 
| 
      
 202 
     | 
    
         
            +
                                cursor_string
         
     | 
| 
      
 203 
     | 
    
         
            +
                              else
         
     | 
| 
      
 204 
     | 
    
         
            +
                                ' ' * cursor_length
         
     | 
| 
      
 205 
     | 
    
         
            +
                              end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                  if scry_session.special_command_targets.any? && scry_session.special_command_targets.map(&:index).include?(index)
         
     | 
| 
      
 208 
     | 
    
         
            +
                    left_fill[-2..-1] = '->'
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
                  left_fill
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                def cursor_length
         
     | 
| 
      
 214 
     | 
    
         
            +
                  tab_length = Scryglass.config.tab_length
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                  consistent_margin = [4 - tab_length, 0].max
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                  (tab_length * depth) + consistent_margin
         
     | 
| 
      
 219 
     | 
    
         
            +
                end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                def cursor_string
         
     | 
| 
      
 222 
     | 
    
         
            +
                  cursor = Scryglass::Session::CURSOR_CHARACTER * cursor_length
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                  if nugget? && has_cursor && value.is_a?(Enumerable) &&
         
     | 
| 
      
 225 
     | 
    
         
            +
                                              value.any? &&
         
     | 
| 
      
 226 
     | 
    
         
            +
                                              enum_sub_ros.empty?
         
     | 
| 
      
 227 
     | 
    
         
            +
                    cursor[0] = '('
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                  if value.instance_variables.any? && iv_sub_ros.empty?
         
     | 
| 
      
 231 
     | 
    
         
            +
                    cursor[1] = '@'
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                  cursor
         
     | 
| 
      
 235 
     | 
    
         
            +
                end
         
     | 
| 
      
 236 
     | 
    
         
            +
              end
         
     | 
| 
      
 237 
     | 
    
         
            +
            end
         
     |