lazy_graph 0.1.3 → 0.2.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 +4 -4
 - data/README.md +282 -47
 - data/Rakefile +12 -1
 - data/examples/converter.rb +41 -0
 - data/examples/performance_tests.rb +9 -9
 - data/examples/shopping_cart_totals.rb +56 -0
 - data/lib/lazy_graph/builder/dsl.rb +67 -30
 - data/lib/lazy_graph/builder.rb +52 -25
 - data/lib/lazy_graph/builder_group.rb +31 -18
 - data/lib/lazy_graph/cli.rb +47 -0
 - data/lib/lazy_graph/context.rb +37 -12
 - data/lib/lazy_graph/environment.rb +6 -0
 - data/lib/lazy_graph/graph.rb +28 -12
 - data/lib/lazy_graph/hash_utils.rb +10 -5
 - data/lib/lazy_graph/logger.rb +87 -0
 - data/lib/lazy_graph/missing_value.rb +9 -2
 - data/lib/lazy_graph/node/array_node.rb +14 -18
 - data/lib/lazy_graph/node/derived_rules.rb +72 -17
 - data/lib/lazy_graph/node/node_properties.rb +34 -9
 - data/lib/lazy_graph/node/object_node.rb +41 -56
 - data/lib/lazy_graph/node/symbol_hash.rb +15 -2
 - data/lib/lazy_graph/node.rb +183 -94
 - data/lib/lazy_graph/path_parser/path.rb +15 -7
 - data/lib/lazy_graph/path_parser/path_group.rb +6 -0
 - data/lib/lazy_graph/path_parser/path_part.rb +8 -0
 - data/lib/lazy_graph/path_parser.rb +1 -1
 - data/lib/lazy_graph/rack_app.rb +138 -0
 - data/lib/lazy_graph/rack_server.rb +199 -0
 - data/lib/lazy_graph/stack_pointer.rb +22 -14
 - data/lib/lazy_graph/version.rb +1 -1
 - data/lib/lazy_graph.rb +6 -8
 - metadata +115 -11
 - data/lib/lazy_graph/server.rb +0 -91
 
    
        data/lib/lazy_graph/graph.rb
    CHANGED
    
    | 
         @@ -4,16 +4,18 @@ require 'json-schema' 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module LazyGraph
         
     | 
| 
       6 
6 
     | 
    
         
             
              # Represents a lazy graph structure based on JSON schema
         
     | 
| 
       7 
     | 
    
         
            -
              VALIDATION_CACHE = {}
         
     | 
| 
      
 7 
     | 
    
         
            +
              VALIDATION_CACHE = {}.compare_by_identity
         
     | 
| 
       8 
8 
     | 
    
         
             
              METASCHEMA = JSON.load_file(File.join(__dir__, 'lazy-graph.json'))
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
              class Graph
         
     | 
| 
       11 
11 
     | 
    
         
             
                attr_reader :json_schema, :root_node, :validate
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
                def context(input) = Context.new(self, input)
         
     | 
| 
       14 
     | 
    
         
            -
                def debug? =  
     | 
| 
      
 14 
     | 
    
         
            +
                def debug? = !!@debug
         
     | 
| 
      
 15 
     | 
    
         
            +
                alias input context
         
     | 
| 
      
 16 
     | 
    
         
            +
                alias feed context
         
     | 
| 
       15 
17 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                def initialize(input_schema, debug: false, validate: true, helpers: nil)
         
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(input_schema, debug: false, validate: true, helpers: nil, namespace: nil)
         
     | 
| 
       17 
19 
     | 
    
         
             
                  @json_schema = HashUtils.deep_dup(input_schema, symbolize: true, signature: signature = [0]).merge(type: :object)
         
     | 
| 
       18 
20 
     | 
    
         
             
                  @debug = debug
         
     | 
| 
       19 
21 
     | 
    
         
             
                  @validate = validate
         
     | 
| 
         @@ -25,18 +27,20 @@ module LazyGraph 
     | 
|
| 
       25 
27 
     | 
    
         
             
                    raise ArgumentError, 'Root schema must be a non-empty object'
         
     | 
| 
       26 
28 
     | 
    
         
             
                  end
         
     | 
| 
       27 
29 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                  @root_node = build_node(@json_schema)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @root_node = build_node(@json_schema, namespace: namespace)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @root_node.build_derived_inputs!
         
     | 
| 
       29 
32 
     | 
    
         
             
                end
         
     | 
| 
       30 
33 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
                def build_node(schema, path = :'$', name = :root, parent = nil)
         
     | 
| 
      
 34 
     | 
    
         
            +
                def build_node(schema, path = :'$', name = :root, parent = nil, namespace: nil)
         
     | 
| 
       32 
35 
     | 
    
         
             
                  schema[:type] = schema[:type].to_sym
         
     | 
| 
       33 
     | 
    
         
            -
                  node =  
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
      
 36 
     | 
    
         
            +
                  node = \
         
     | 
| 
      
 37 
     | 
    
         
            +
                    case schema[:type]
         
     | 
| 
      
 38 
     | 
    
         
            +
                    when :object then ObjectNode
         
     | 
| 
      
 39 
     | 
    
         
            +
                    when :array then ArrayNode
         
     | 
| 
      
 40 
     | 
    
         
            +
                    else Node
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end.new(name, path, schema, parent, debug: @debug, helpers: @helpers, namespace: namespace)
         
     | 
| 
       38 
42 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                  if node.type 
     | 
| 
      
 43 
     | 
    
         
            +
                  if node.type.equal?(:object)
         
     | 
| 
       40 
44 
     | 
    
         
             
                    node.children = \
         
     | 
| 
       41 
45 
     | 
    
         
             
                      {
         
     | 
| 
       42 
46 
     | 
    
         
             
                        properties: schema.fetch(:properties, {}).map do |key, value|
         
     | 
| 
         @@ -46,7 +50,7 @@ module LazyGraph 
     | 
|
| 
       46 
50 
     | 
    
         
             
                          [Regexp.new(key.to_s), build_node(value, :"#{path}.#{key}", :'<property>', node)]
         
     | 
| 
       47 
51 
     | 
    
         
             
                        end
         
     | 
| 
       48 
52 
     | 
    
         
             
                      }
         
     | 
| 
       49 
     | 
    
         
            -
                  elsif node.type 
     | 
| 
      
 53 
     | 
    
         
            +
                  elsif node.type.equal?(:array)
         
     | 
| 
       50 
54 
     | 
    
         
             
                    node.children = build_node(schema.fetch(:items, {}), :"#{path}[]", :items, node)
         
     | 
| 
       51 
55 
     | 
    
         
             
                  end
         
     | 
| 
       52 
56 
     | 
    
         
             
                  node
         
     | 
| 
         @@ -54,6 +58,18 @@ module LazyGraph 
     | 
|
| 
       54 
58 
     | 
    
         | 
| 
       55 
59 
     | 
    
         
             
                def validate!(input, schema = @json_schema)
         
     | 
| 
       56 
60 
     | 
    
         
             
                  JSON::Validator.validate!(schema, input)
         
     | 
| 
      
 61 
     | 
    
         
            +
                rescue JSON::Schema::ValidationError => e
         
     | 
| 
      
 62 
     | 
    
         
            +
                  raise ValidationError, "Input validation failed: #{e.message}", cause: e
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def pretty_print(q)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # Start the custom pretty print
         
     | 
| 
      
 67 
     | 
    
         
            +
                  q.group(1, '<LazyGraph::Graph ', '>') do
         
     | 
| 
      
 68 
     | 
    
         
            +
                    q.group do
         
     | 
| 
      
 69 
     | 
    
         
            +
                      q.text 'props='
         
     | 
| 
      
 70 
     | 
    
         
            +
                      q.text root_node.children[:properties].keys
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
       57 
73 
     | 
    
         
             
                end
         
     | 
| 
       58 
74 
     | 
    
         
             
              end
         
     | 
| 
       59 
75 
     | 
    
         | 
| 
         @@ -20,6 +20,8 @@ module LazyGraph 
     | 
|
| 
       20 
20 
     | 
    
         
             
                      signature[0] ^= key.object_id if signature
         
     | 
| 
       21 
21 
     | 
    
         
             
                      result[key] = deep_dup(value, symbolize: symbolize, signature: signature)
         
     | 
| 
       22 
22 
     | 
    
         
             
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  when Struct
         
     | 
| 
      
 24 
     | 
    
         
            +
                    deep_dup(obj.to_h, symbolize: symbolize, signature: signature)
         
     | 
| 
       23 
25 
     | 
    
         
             
                  when Array
         
     | 
| 
       24 
26 
     | 
    
         
             
                    obj.map { |value| deep_dup(value, symbolize: symbolize, signature: signature) }
         
     | 
| 
       25 
27 
     | 
    
         
             
                  when String, Numeric, TrueClass, FalseClass, NilClass
         
     | 
| 
         @@ -47,7 +49,7 @@ module LazyGraph 
     | 
|
| 
       47 
49 
     | 
    
         
             
                  end
         
     | 
| 
       48 
50 
     | 
    
         
             
                end
         
     | 
| 
       49 
51 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                def  
     | 
| 
      
 52 
     | 
    
         
            +
                def strip_missing(obj, parent_list = {}.compare_by_identity)
         
     | 
| 
       51 
53 
     | 
    
         
             
                  return { '^ref': :circular } if (circular_dependency = parent_list[obj])
         
     | 
| 
       52 
54 
     | 
    
         | 
| 
       53 
55 
     | 
    
         
             
                  parent_list[obj] = true
         
     | 
| 
         @@ -55,18 +57,21 @@ module LazyGraph 
     | 
|
| 
       55 
57 
     | 
    
         
             
                  when Hash
         
     | 
| 
       56 
58 
     | 
    
         
             
                    obj.each_with_object({}) do |(key, value), obj|
         
     | 
| 
       57 
59 
     | 
    
         
             
                      next if value.is_a?(MissingValue)
         
     | 
| 
      
 60 
     | 
    
         
            +
                      next if value.nil?
         
     | 
| 
       58 
61 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                      obj[key] =  
     | 
| 
      
 62 
     | 
    
         
            +
                      obj[key] = strip_missing(value, parent_list)
         
     | 
| 
       60 
63 
     | 
    
         
             
                    end
         
     | 
| 
       61 
64 
     | 
    
         
             
                  when Struct
         
     | 
| 
       62 
65 
     | 
    
         
             
                    obj.members.each_with_object({}) do |key, res|
         
     | 
| 
       63 
     | 
    
         
            -
                       
     | 
| 
      
 66 
     | 
    
         
            +
                      value = obj.original_get(key)
         
     | 
| 
      
 67 
     | 
    
         
            +
                      next if value.is_a?(MissingValue)
         
     | 
| 
      
 68 
     | 
    
         
            +
                      next if value.nil?
         
     | 
| 
       64 
69 
     | 
    
         
             
                      next if obj.invisible.include?(key)
         
     | 
| 
       65 
70 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
                      res[key] =  
     | 
| 
      
 71 
     | 
    
         
            +
                      res[key] = strip_missing(obj[key], parent_list)
         
     | 
| 
       67 
72 
     | 
    
         
             
                    end
         
     | 
| 
       68 
73 
     | 
    
         
             
                  when Array
         
     | 
| 
       69 
     | 
    
         
            -
                    obj.map { |value|  
     | 
| 
      
 74 
     | 
    
         
            +
                    obj.map { |value| strip_missing(value, parent_list) }
         
     | 
| 
       70 
75 
     | 
    
         
             
                  when MissingValue
         
     | 
| 
       71 
76 
     | 
    
         
             
                    nil
         
     | 
| 
       72 
77 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -0,0 +1,87 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'environment'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module LazyGraph
         
     | 
| 
      
 5 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :logger
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              module Logger
         
     | 
| 
      
 10 
     | 
    
         
            +
                COLORIZED_LOGS = !ENV['DISABLED_COLORIZED_LOGS'] && ENV.fetch('RACK_ENV', 'development') == 'development'
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                module_function
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_accessor :color_enabled, :structured
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def structured
         
     | 
| 
      
 19 
     | 
    
         
            +
                  return @structured if defined?(@structured)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @structured = !LazyGraph::Environment.development?
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def default_logger
         
     | 
| 
      
 24 
     | 
    
         
            +
                  logger = ::Logger.new($stdout)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  self.color_enabled ||= Logger::COLORIZED_LOGS
         
     | 
| 
      
 26 
     | 
    
         
            +
                  if self.color_enabled
         
     | 
| 
      
 27 
     | 
    
         
            +
                    logger.formatter = proc do |severity, datetime, progname, message|
         
     | 
| 
      
 28 
     | 
    
         
            +
                      light_gray_timestamp = "\e[90m[##{Process.pid}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%6N')}\e[0m" # Light gray timestamp
         
     | 
| 
      
 29 
     | 
    
         
            +
                      "#{light_gray_timestamp} \e[1m#{severity}\e[0m #{progname}: #{message}\n"
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  elsif self.structured
         
     | 
| 
      
 32 
     | 
    
         
            +
                    logger.formatter = proc do |severity, datetime, progname, message|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      "#{{severity:, datetime:, progname:, **(message.is_a?(Hash) ? message : {message: }) }.to_json}\n"
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  else
         
     | 
| 
      
 36 
     | 
    
         
            +
                    logger.formatter = proc do |severity, datetime, progname, message|
         
     | 
| 
      
 37 
     | 
    
         
            +
                      "[##{Process.pid}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%6N')} #{severity} #{progname}: #{message}\n"
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  logger
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def build_color_string(&blk)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  return unless block_given?
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  instance_eval(&blk)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def colorize(text, color_code)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @color_enabled ? "\e[#{color_code}m#{text}\e[0m" : text
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                def green(text)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  colorize(text, 32) # Green for success
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def red(text)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  colorize(text, 31) # Red for errors
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def yellow(text)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  colorize(text, 33) # Yellow for warnings or debug
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def blue(text)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  colorize(text, 34) # Blue for info
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def light_gray(text)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  colorize(text, 90) # Light gray for faded text
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def orange(text)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  colorize(text, '38;5;214')
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                def bold(text)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  colorize(text, 1) # Bold text
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                def dim(text)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  colorize(text, 2) # Italic text
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              self.logger = Logger.default_logger
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -8,13 +8,20 @@ module LazyGraph 
     | 
|
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
                def initialize(details) = @details = details
         
     | 
| 
       10 
10 
     | 
    
         
             
                def to_s = "MISSING[#{@details}]"
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
11 
     | 
    
         
             
                def inspect = to_s
         
     | 
| 
       13 
12 
     | 
    
         
             
                def coerce(other) = [self, other]
         
     | 
| 
       14 
13 
     | 
    
         
             
                def as_json = nil
         
     | 
| 
      
 14 
     | 
    
         
            +
                def to_h = nil
         
     | 
| 
       15 
15 
     | 
    
         
             
                def +(other) = other
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
16 
     | 
    
         
             
                def respond_to_missing?(_method_name, _include_private = false) = true
         
     | 
| 
      
 17 
     | 
    
         
            +
                def to_i = 0
         
     | 
| 
      
 18 
     | 
    
         
            +
                def to_f = 0.0
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return true if other.nil?
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  super
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
       18 
25 
     | 
    
         | 
| 
       19 
26 
     | 
    
         
             
                def method_missing(method, *args, &block)
         
     | 
| 
       20 
27 
     | 
    
         
             
                  return super if method == :to_ary
         
     | 
| 
         @@ -14,42 +14,38 @@ module LazyGraph 
     | 
|
| 
       14 
14 
     | 
    
         
             
                  should_recycle = stack_memory,
         
     | 
| 
       15 
15 
     | 
    
         
             
                  **
         
     | 
| 
       16 
16 
     | 
    
         
             
                )
         
     | 
| 
       17 
     | 
    
         
            -
                  input = stack_memory.frame
         
     | 
| 
      
 17 
     | 
    
         
            +
                  return MissingValue() unless input = stack_memory.frame
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       18 
19 
     | 
    
         
             
                  @visited[input.object_id >> 2 ^ path.shifted_id] ||= begin
         
     | 
| 
      
 20 
     | 
    
         
            +
                    path_next = path.next
         
     | 
| 
       19 
21 
     | 
    
         
             
                    if (path_segment = path.segment).is_a?(PathParser::PathGroup)
         
     | 
| 
       20 
22 
     | 
    
         
             
                      unless path_segment.index?
         
     | 
| 
       21 
23 
     | 
    
         
             
                        return input.length.times.map do |index|
         
     | 
| 
       22 
     | 
    
         
            -
                           
     | 
| 
       23 
     | 
    
         
            -
                          children.resolve(path, stack_memory.push(item, index))
         
     | 
| 
      
 24 
     | 
    
         
            +
                          children.fetch_and_resolve(path, input, index, stack_memory)
         
     | 
| 
       24 
25 
     | 
    
         
             
                        end
         
     | 
| 
       25 
26 
     | 
    
         
             
                      end
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
                      return resolve(path_segment.options.first.merge( 
     | 
| 
      
 28 
     | 
    
         
            +
                      return resolve(path_segment.options.first.merge(path_next), stack_memory, nil) if path_segment.options.one?
         
     | 
| 
       28 
29 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                      return path_segment.options.map { |part| resolve(part.merge( 
     | 
| 
      
 30 
     | 
    
         
            +
                      return path_segment.options.map { |part| resolve(part.merge(path_next), stack_memory, nil) }
         
     | 
| 
       30 
31 
     | 
    
         
             
                    end
         
     | 
| 
       31 
32 
     | 
    
         | 
| 
       32 
33 
     | 
    
         
             
                    segment = path_segment&.part
         
     | 
| 
       33 
34 
     | 
    
         
             
                    case segment
         
     | 
| 
       34 
35 
     | 
    
         
             
                    when nil
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                      unless @children.simple?
         
     | 
| 
      
 38 
     | 
    
         
            +
                        input.length.times do |index|
         
     | 
| 
      
 39 
     | 
    
         
            +
                          @children.fetch_and_resolve(path, input, index, stack_memory)
         
     | 
| 
      
 40 
     | 
    
         
            +
                        end
         
     | 
| 
       38 
41 
     | 
    
         
             
                      end
         
     | 
| 
       39 
42 
     | 
    
         
             
                      input
         
     | 
| 
       40 
43 
     | 
    
         
             
                    when DIGIT_REGEXP
         
     | 
| 
       41 
     | 
    
         
            -
                       
     | 
| 
       42 
     | 
    
         
            -
                      children.resolve(path.next, stack_memory.push(item, segment))
         
     | 
| 
       43 
     | 
    
         
            -
                    when :*
         
     | 
| 
       44 
     | 
    
         
            -
                      input.length.times.map do |index|
         
     | 
| 
       45 
     | 
    
         
            -
                        item = children.fetch_item(input, index, stack_memory)
         
     | 
| 
       46 
     | 
    
         
            -
                        @children.resolve(path.next, stack_memory.push(item, index))
         
     | 
| 
       47 
     | 
    
         
            -
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @children.fetch_and_resolve(path_next, input, segment.to_s.to_i, stack_memory)
         
     | 
| 
       48 
45 
     | 
    
         
             
                    else
         
     | 
| 
       49 
46 
     | 
    
         
             
                      if @child_properties&.key?(segment) || input&.first&.key?(segment)
         
     | 
| 
       50 
47 
     | 
    
         
             
                        input.length.times.map do |index|
         
     | 
| 
       51 
     | 
    
         
            -
                           
     | 
| 
       52 
     | 
    
         
            -
                          @children.resolve(path, stack_memory.push(item, index))
         
     | 
| 
      
 48 
     | 
    
         
            +
                          @children.fetch_and_resolve(path, input, index, stack_memory)
         
     | 
| 
       53 
49 
     | 
    
         
             
                        end
         
     | 
| 
       54 
50 
     | 
    
         
             
                      else
         
     | 
| 
       55 
51 
     | 
    
         
             
                        MissingValue()
         
     | 
| 
         @@ -66,7 +62,7 @@ module LazyGraph 
     | 
|
| 
       66 
62 
     | 
    
         
             
                end
         
     | 
| 
       67 
63 
     | 
    
         | 
| 
       68 
64 
     | 
    
         
             
                def cast(value)
         
     | 
| 
       69 
     | 
    
         
            -
                  value
         
     | 
| 
      
 65 
     | 
    
         
            +
                  Array(value)
         
     | 
| 
       70 
66 
     | 
    
         
             
                end
         
     | 
| 
       71 
67 
     | 
    
         
             
              end
         
     | 
| 
       72 
68 
     | 
    
         
             
            end
         
     | 
| 
         @@ -25,6 +25,7 @@ module LazyGraph 
     | 
|
| 
       25 
25 
     | 
    
         
             
                  def build_derived_inputs(derived, helpers)
         
     | 
| 
       26 
26 
     | 
    
         
             
                    @resolvers = {}.compare_by_identity
         
     | 
| 
       27 
27 
     | 
    
         
             
                    @path_cache = {}.compare_by_identity
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @resolution_stack = {}.compare_by_identity
         
     | 
| 
       28 
29 
     | 
    
         | 
| 
       29 
30 
     | 
    
         
             
                    derived = interpret_derived_proc(derived) if derived.is_a?(Proc)
         
     | 
| 
       30 
31 
     | 
    
         
             
                    derived = { inputs: derived.to_s } if derived.is_a?(String) || derived.is_a?(Symbol)
         
     | 
| 
         @@ -43,9 +44,10 @@ module LazyGraph 
     | 
|
| 
       43 
44 
     | 
    
         
             
                  end
         
     | 
| 
       44 
45 
     | 
    
         | 
| 
       45 
46 
     | 
    
         
             
                  def interpret_derived_proc(derived)
         
     | 
| 
       46 
     | 
    
         
            -
                    src, requireds, optionals, keywords,  
     | 
| 
       47 
     | 
    
         
            -
                     
     | 
| 
       48 
     | 
    
         
            -
                    @src =  
     | 
| 
      
 47 
     | 
    
         
            +
                    src, requireds, optionals, keywords, loc = DerivedRules.extract_expr_from_source_location(derived.source_location)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    body = src.body&.slice || ''
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @src = body.lines.map(&:strip)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    offset = src.slice.lines.length - body.lines.length
         
     | 
| 
       49 
51 
     | 
    
         
             
                    inputs, conditions = parse_args_with_conditions(requireds, optionals, keywords)
         
     | 
| 
       50 
52 
     | 
    
         | 
| 
       51 
53 
     | 
    
         
             
                    {
         
     | 
| 
         @@ -53,11 +55,11 @@ module LazyGraph 
     | 
|
| 
       53 
55 
     | 
    
         
             
                      mtime: File.mtime(derived.source_location.first),
         
     | 
| 
       54 
56 
     | 
    
         
             
                      conditions: conditions,
         
     | 
| 
       55 
57 
     | 
    
         
             
                      calc: instance_eval(
         
     | 
| 
       56 
     | 
    
         
            -
                        "->(#{inputs.keys.map { |k| "#{k}=self.#{k}" }.join(', ')}){ #{ 
     | 
| 
      
 58 
     | 
    
         
            +
                        "->(#{inputs.keys.map { |k| "#{k}=self.#{k}" }.join(', ')}){ #{body}}",
         
     | 
| 
       57 
59 
     | 
    
         
             
                        # rubocop:disable:next-line
         
     | 
| 
       58 
60 
     | 
    
         
             
                        derived.source_location.first,
         
     | 
| 
       59 
61 
     | 
    
         
             
                        # rubocop:enable
         
     | 
| 
       60 
     | 
    
         
            -
                        derived.source_location.last 
     | 
| 
      
 62 
     | 
    
         
            +
                        derived.source_location.last + offset
         
     | 
| 
       61 
63 
     | 
    
         
             
                      )
         
     | 
| 
       62 
64 
     | 
    
         
             
                    }
         
     | 
| 
       63 
65 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -77,13 +79,21 @@ module LazyGraph 
     | 
|
| 
       77 
79 
     | 
    
         
             
                    [keywords, conditions.any? ? conditions : nil]
         
     | 
| 
       78 
80 
     | 
    
         
             
                  end
         
     | 
| 
       79 
81 
     | 
    
         | 
| 
      
 82 
     | 
    
         
            +
                  def self.get_file_body(file_path)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @file_body_cache ||= {}
         
     | 
| 
      
 84 
     | 
    
         
            +
                    if @file_body_cache[file_path]&.last.to_i < File.mtime(file_path).to_i
         
     | 
| 
      
 85 
     | 
    
         
            +
                      @file_body_cache[file_path] = [IO.readlines(file_path), File.mtime(file_path).to_i]
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @file_body_cache[file_path]&.first
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
       80 
90 
     | 
    
         
             
                  def self.extract_expr_from_source_location(source_location)
         
     | 
| 
       81 
91 
     | 
    
         
             
                    @derived_proc_cache ||= {}
         
     | 
| 
       82 
92 
     | 
    
         
             
                    mtime = File.mtime(source_location.first).to_i
         
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
93 
     | 
    
         
             
                    if @derived_proc_cache[source_location]&.last.to_i.< mtime
         
     | 
| 
       85 
94 
     | 
    
         
             
                      @derived_proc_cache[source_location] = begin
         
     | 
| 
       86 
     | 
    
         
            -
                        source_lines =  
     | 
| 
      
 95 
     | 
    
         
            +
                        source_lines = get_file_body(source_location.first)
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
       87 
97 
     | 
    
         
             
                        proc_line = source_location.last - 1
         
     | 
| 
       88 
98 
     | 
    
         
             
                        first_line = source_lines[proc_line]
         
     | 
| 
       89 
99 
     | 
    
         
             
                        until first_line =~ /(?:lambda|proc|->)/ || proc_line.zero?
         
     | 
| 
         @@ -124,7 +134,7 @@ module LazyGraph 
     | 
|
| 
       124 
134 
     | 
    
         
             
                    @derived_proc_cache[source_location]
         
     | 
| 
       125 
135 
     | 
    
         
             
                  rescue StandardError => e
         
     | 
| 
       126 
136 
     | 
    
         
             
                    LazyGraph.logger.error(e.message)
         
     | 
| 
       127 
     | 
    
         
            -
                    LazyGraph.logger.error(e.backtrace)
         
     | 
| 
      
 137 
     | 
    
         
            +
                    LazyGraph.logger.error(e.backtrace.join("\n"))
         
     | 
| 
       128 
138 
     | 
    
         
             
                    raise "Failed to extract expression from source location: #{source_location}. Ensure the file exists and the line number is correct. Extraction from a REPL is not supported"
         
     | 
| 
       129 
139 
     | 
    
         
             
                  end
         
     | 
| 
       130 
140 
     | 
    
         | 
| 
         @@ -132,15 +142,16 @@ module LazyGraph 
     | 
|
| 
       132 
142 
     | 
    
         
             
                    inputs = derived[:inputs]
         
     | 
| 
       133 
143 
     | 
    
         
             
                    case inputs
         
     | 
| 
       134 
144 
     | 
    
         
             
                    when Symbol, String
         
     | 
| 
       135 
     | 
    
         
            -
                      if  
     | 
| 
      
 145 
     | 
    
         
            +
                      if !derived[:calc]
         
     | 
| 
       136 
146 
     | 
    
         
             
                        @src ||= inputs
         
     | 
| 
       137 
147 
     | 
    
         
             
                        input_hash = {}
         
     | 
| 
       138 
148 
     | 
    
         
             
                        @input_mapper = {}
         
     | 
| 
       139 
     | 
    
         
            -
                         
     | 
| 
      
 149 
     | 
    
         
            +
                        calc = inputs.gsub(PLACEHOLDER_VAR_REGEX) do |match|
         
     | 
| 
       140 
150 
     | 
    
         
             
                          sub = input_hash[match[2...-1]] ||= "a#{::SecureRandom.hex(8)}"
         
     | 
| 
       141 
151 
     | 
    
         
             
                          @input_mapper[sub.to_sym] = match[2...-1].to_sym
         
     | 
| 
       142 
152 
     | 
    
         
             
                          sub
         
     | 
| 
       143 
153 
     | 
    
         
             
                        end
         
     | 
| 
      
 154 
     | 
    
         
            +
                        derived[:calc] = calc unless calc == input_hash.values.first
         
     | 
| 
       144 
155 
     | 
    
         
             
                        input_hash.invert
         
     | 
| 
       145 
156 
     | 
    
         
             
                      else
         
     | 
| 
       146 
157 
     | 
    
         
             
                        { inputs.to_s.gsub(/[^(?:[A-Za-z][A-Za-z0-9_])]/, '__') => inputs.to_s.freeze }
         
     | 
| 
         @@ -167,10 +178,20 @@ module LazyGraph 
     | 
|
| 
       167 
178 
     | 
    
         | 
| 
       168 
179 
     | 
    
         
             
                  def parse_rule_string(derived)
         
     | 
| 
       169 
180 
     | 
    
         
             
                    calc_str = derived[:calc]
         
     | 
| 
       170 
     | 
    
         
            -
                     
     | 
| 
       171 
     | 
    
         
            -
             
     | 
| 
       172 
     | 
    
         
            -
             
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
      
 181 
     | 
    
         
            +
                    node_path = path
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                    src = <<~RUBY, @rule_location&.first, @rule_location&.last.to_i - 2
         
     | 
| 
      
 184 
     | 
    
         
            +
                      ->{
         
     | 
| 
      
 185 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 186 
     | 
    
         
            +
                          #{calc_str}
         
     | 
| 
      
 187 
     | 
    
         
            +
                        rescue StandardError => e;
         
     | 
| 
      
 188 
     | 
    
         
            +
                          LazyGraph.logger.error("Exception in \#{calc_str} => \#{node_path}. \#{e.message}")
         
     | 
| 
      
 189 
     | 
    
         
            +
                          raise e
         
     | 
| 
      
 190 
     | 
    
         
            +
                        end
         
     | 
| 
      
 191 
     | 
    
         
            +
                        }
         
     | 
| 
      
 192 
     | 
    
         
            +
                    RUBY
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                    instance_eval(*src)
         
     | 
| 
       174 
195 
     | 
    
         
             
                  rescue SyntaxError
         
     | 
| 
       175 
196 
     | 
    
         
             
                    missing_value = MissingValue { "Syntax error in #{derived[:src]}" }
         
     | 
| 
       176 
197 
     | 
    
         
             
                    -> { missing_value }
         
     | 
| 
         @@ -194,12 +215,46 @@ module LazyGraph 
     | 
|
| 
       194 
215 
     | 
    
         
             
                    end.new
         
     | 
| 
       195 
216 
     | 
    
         
             
                  end
         
     | 
| 
       196 
217 
     | 
    
         | 
| 
      
 218 
     | 
    
         
            +
                  def resolver_for(path)
         
     | 
| 
      
 219 
     | 
    
         
            +
                    segment = path.segment.part
         
     | 
| 
      
 220 
     | 
    
         
            +
                    return root.properties[path.next.segment.part] if segment == :'$'
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                    (segment == name ? parent.parent : @parent).find_resolver_for(segment)
         
     | 
| 
      
 223 
     | 
    
         
            +
                  end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                  def rule_definition_backtrace
         
     | 
| 
      
 226 
     | 
    
         
            +
                    if @rule_location && @rule_location.size >= 2
         
     | 
| 
      
 227 
     | 
    
         
            +
                      rule_file, rule_line = @rule_location
         
     | 
| 
      
 228 
     | 
    
         
            +
                      rule_entry = "#{rule_file}:#{rule_line}:in `rule`"
         
     | 
| 
      
 229 
     | 
    
         
            +
                    else
         
     | 
| 
      
 230 
     | 
    
         
            +
                      rule_entry = 'unknown_rule_location'
         
     | 
| 
      
 231 
     | 
    
         
            +
                    end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                    current_backtrace = caller.reverse.take_while { |line| !line.include?('/lib/lazy_graph/') }.reverse
         
     | 
| 
      
 234 
     | 
    
         
            +
                    [rule_entry] + current_backtrace
         
     | 
| 
      
 235 
     | 
    
         
            +
                  end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
       197 
237 
     | 
    
         
             
                  def map_derived_inputs_to_paths(inputs)
         
     | 
| 
       198 
238 
     | 
    
         
             
                    inputs.values.map.with_index do |path, idx|
         
     | 
| 
       199 
     | 
    
         
            -
                       
     | 
| 
       200 
     | 
    
         
            -
                        segment.is_a?(PathParser::PathGroup) && 
     | 
| 
      
 239 
     | 
    
         
            +
                      segments = path.parts.map.with_index do |segment, i|
         
     | 
| 
      
 240 
     | 
    
         
            +
                        if segment.is_a?(PathParser::PathGroup) &&
         
     | 
| 
      
 241 
     | 
    
         
            +
                           segment.options.length == 1 && !((resolver = resolver_for(segment.options.first)) || segment.options.first.segment.part.to_s =~ /\d+/)
         
     | 
| 
      
 242 
     | 
    
         
            +
                          raise(ValidationError.new(
         
     | 
| 
      
 243 
     | 
    
         
            +
                            "Invalid dependency in #{@path}: #{segment.options.first.to_path_str}  cannot be resolved."
         
     | 
| 
      
 244 
     | 
    
         
            +
                          ).tap { |e| e.set_backtrace(rule_definition_backtrace) })
         
     | 
| 
      
 245 
     | 
    
         
            +
                        end
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                        resolver ? [i, resolver] : nil
         
     | 
| 
       201 
248 
     | 
    
         
             
                      end.compact
         
     | 
| 
       202 
     | 
    
         
            -
                       
     | 
| 
      
 249 
     | 
    
         
            +
                      resolver = resolver_for(path)
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                      unless resolver
         
     | 
| 
      
 252 
     | 
    
         
            +
                        raise(ValidationError.new(
         
     | 
| 
      
 253 
     | 
    
         
            +
                          "Invalid dependency in #{@path}: #{path.to_path_str}  cannot be resolved."
         
     | 
| 
      
 254 
     | 
    
         
            +
                        ).tap { |e| e.set_backtrace(rule_definition_backtrace) })
         
     | 
| 
      
 255 
     | 
    
         
            +
                      end
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                      [path, resolver, idx, segments.any? ? segments : nil]
         
     | 
| 
       203 
258 
     | 
    
         
             
                    end
         
     | 
| 
       204 
259 
     | 
    
         
             
                  end
         
     | 
| 
       205 
260 
     | 
    
         
             
                end
         
     | 
| 
         @@ -8,14 +8,27 @@ module LazyGraph 
     | 
|
| 
       8 
8 
     | 
    
         
             
                      members.each { |k| self[k] = kws[k].then { |v| v.nil? ? MissingValue::BLANK : v } }
         
     | 
| 
       9 
9 
     | 
    
         
             
                    end
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
      
 11 
     | 
    
         
            +
                    members.each do |m|
         
     | 
| 
      
 12 
     | 
    
         
            +
                      define_method(m) do
         
     | 
| 
      
 13 
     | 
    
         
            +
                        self[m]
         
     | 
| 
      
 14 
     | 
    
         
            +
                      end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    alias_method :original_get, :[]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       11 
19 
     | 
    
         
             
                    define_method(:key?) do |x|
         
     | 
| 
       12 
     | 
    
         
            -
                      ! 
     | 
| 
      
 20 
     | 
    
         
            +
                      !original_get(x).equal?(MissingValue::BLANK)
         
     | 
| 
       13 
21 
     | 
    
         
             
                    end
         
     | 
| 
       14 
22 
     | 
    
         | 
| 
       15 
23 
     | 
    
         
             
                    define_method(:[]=) do |key, val|
         
     | 
| 
       16 
24 
     | 
    
         
             
                      super(key, val)
         
     | 
| 
       17 
25 
     | 
    
         
             
                    end
         
     | 
| 
       18 
26 
     | 
    
         | 
| 
      
 27 
     | 
    
         
            +
                    define_method(:[]) do |key|
         
     | 
| 
      
 28 
     | 
    
         
            +
                      res = original_get(key)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      res.is_a?(MissingValue) ? nil : res
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       19 
32 
     | 
    
         
             
                    define_method(:members) do
         
     | 
| 
       20 
33 
     | 
    
         
             
                      members
         
     | 
| 
       21 
34 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -28,6 +41,17 @@ module LazyGraph 
     | 
|
| 
       28 
41 
     | 
    
         
             
                      to_h
         
     | 
| 
       29 
42 
     | 
    
         
             
                    end
         
     | 
| 
       30 
43 
     | 
    
         | 
| 
      
 44 
     | 
    
         
            +
                    def to_h
         
     | 
| 
      
 45 
     | 
    
         
            +
                      HashUtils.strip_missing(self)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    def ==(other)
         
     | 
| 
      
 49 
     | 
    
         
            +
                      return super if other.is_a?(self.class)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      return to_h.eql?(other.to_h.keep_if { |_, v| !v.nil? }) if other.respond_to?(:to_h)
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      super
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
       31 
55 
     | 
    
         
             
                    define_method(:each_key, &members.method(:each))
         
     | 
| 
       32 
56 
     | 
    
         | 
| 
       33 
57 
     | 
    
         
             
                    def dup
         
     | 
| 
         @@ -36,23 +60,24 @@ module LazyGraph 
     | 
|
| 
       36 
60 
     | 
    
         | 
| 
       37 
61 
     | 
    
         
             
                    def get_first_of(*props)
         
     | 
| 
       38 
62 
     | 
    
         
             
                      key = props.find do |prop|
         
     | 
| 
       39 
     | 
    
         
            -
                        ! 
     | 
| 
      
 63 
     | 
    
         
            +
                        !original_get(prop).is_a?(MissingValue)
         
     | 
| 
       40 
64 
     | 
    
         
             
                      end
         
     | 
| 
       41 
65 
     | 
    
         
             
                      key ? self[key] : MissingValue::BLANK
         
     | 
| 
       42 
66 
     | 
    
         
             
                    end
         
     | 
| 
       43 
67 
     | 
    
         | 
| 
       44 
68 
     | 
    
         
             
                    def pretty_print(q)
         
     | 
| 
       45 
     | 
    
         
            -
                      q.group(1, '< 
     | 
| 
      
 69 
     | 
    
         
            +
                      q.group(1, '<', '>') do
         
     | 
| 
      
 70 
     | 
    
         
            +
                        q.text "#{self.class.name} "
         
     | 
| 
       46 
71 
     | 
    
         
             
                        q.seplist(members.zip(values).reject do |m, v|
         
     | 
| 
       47 
     | 
    
         
            -
                          m == :DEBUG  
     | 
| 
       48 
     | 
    
         
            -
                        end) do | 
     | 
| 
       49 
     | 
    
         
            -
                          q. 
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                            value.respond_to?(:pretty_print) ? q.pp(value) : q.text(value.inspect)
         
     | 
| 
       52 
     | 
    
         
            -
                          end
         
     | 
| 
      
 72 
     | 
    
         
            +
                          m == :DEBUG || v.nil? || v.is_a?(MissingValue)
         
     | 
| 
      
 73 
     | 
    
         
            +
                        end.to_h) do |k, v|
         
     | 
| 
      
 74 
     | 
    
         
            +
                          q.text "#{k}: "
         
     | 
| 
      
 75 
     | 
    
         
            +
                          q.pp v
         
     | 
| 
       53 
76 
     | 
    
         
             
                        end
         
     | 
| 
       54 
77 
     | 
    
         
             
                      end
         
     | 
| 
       55 
78 
     | 
    
         
             
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    alias_method :keys, :members
         
     | 
| 
       56 
81 
     | 
    
         
             
                  end
         
     | 
| 
       57 
82 
     | 
    
         
             
                end
         
     | 
| 
       58 
83 
     | 
    
         
             
              end
         
     |