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
|