lazy_graph 0.1.3 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/performance_tests.rb +7 -7
- data/lib/lazy_graph/builder/dsl.rb +0 -1
- data/lib/lazy_graph/builder.rb +19 -12
- data/lib/lazy_graph/builder_group.rb +8 -6
- data/lib/lazy_graph/context.rb +31 -10
- data/lib/lazy_graph/graph.rb +24 -10
- data/lib/lazy_graph/hash_utils.rb +6 -4
- data/lib/lazy_graph/missing_value.rb +1 -2
- data/lib/lazy_graph/node/array_node.rb +11 -16
- data/lib/lazy_graph/node/derived_rules.rb +19 -6
- data/lib/lazy_graph/node/node_properties.rb +19 -7
- data/lib/lazy_graph/node/object_node.rb +33 -52
- data/lib/lazy_graph/node.rb +95 -64
- 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/server.rb +5 -0
- data/lib/lazy_graph/stack_pointer.rb +22 -14
- data/lib/lazy_graph/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cb285016525832f149dd41b91340c71f6f6c68d7aebcc0de6376dbf81c71cd8
|
4
|
+
data.tar.gz: fc0bee81e2e2b45dfad51c822c421d0496bda877d8e77598d29875b66f261286
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d156ef5344d260232771fa272d5a318342a46293a5991abb9c47948fd0b79c096f2f1f219cf9383a677435ec0e9e9f1bc95011b254d9828e2b19b86f991a5fe
|
7
|
+
data.tar.gz: e9c44f0ff222e41ad1992703b3f8adf1a4424f073bc80b89bb1d0f85f515fbe8413d023f9072141fbeaff5b242bf9e91fd415c43b8445cc10d2754a17af73b0a
|
@@ -13,9 +13,9 @@ class PerformanceBuilder < LazyGraph::Builder
|
|
13
13
|
string :pay_schedule_id, invisible: true
|
14
14
|
string :position_id, invisible: true
|
15
15
|
|
16
|
-
object :position, rule: :"
|
17
|
-
object :pay_schedule, rule: :'
|
18
|
-
number :pay_rate, rule: :"
|
16
|
+
object :position, rule: :"$.positions[position_id]"
|
17
|
+
object :pay_schedule, rule: :'pay_schedules[pay_schedule_id]'
|
18
|
+
number :pay_rate, rule: :"position.pay_rate"
|
19
19
|
string :employee_id, rule: :id
|
20
20
|
end
|
21
21
|
end
|
@@ -67,7 +67,7 @@ def profile_n(n, debug: false, validate: false)
|
|
67
67
|
graph = PerformanceBuilder.performance.build!(debug: debug, validate: validate)
|
68
68
|
Vernier.profile(out: './examples/time_profile.json') do
|
69
69
|
start = Time.now
|
70
|
-
graph.context(employees).
|
70
|
+
graph.context(employees).get('')
|
71
71
|
ends = Time.now
|
72
72
|
puts "Time elapsed: #{ends - start}"
|
73
73
|
end
|
@@ -77,7 +77,7 @@ def memory_profile_n(n, debug: false, validate: false)
|
|
77
77
|
employees = gen_employees(n)
|
78
78
|
graph = PerformanceBuilder.performance.build!(debug: debug, validate: validate)
|
79
79
|
report = MemoryProfiler.report do
|
80
|
-
graph.context(employees).
|
80
|
+
graph.context(employees).get('')
|
81
81
|
end
|
82
82
|
report.pretty_print
|
83
83
|
end
|
@@ -87,7 +87,7 @@ def benchmark_ips_n(n, debug: false, validate: false)
|
|
87
87
|
employees = gen_employees(n)
|
88
88
|
Benchmark.ips do |x|
|
89
89
|
x.report('performance') do
|
90
|
-
graph.context(employees).
|
90
|
+
graph.context(employees).get('')
|
91
91
|
end
|
92
92
|
x.compare!
|
93
93
|
end
|
@@ -97,7 +97,7 @@ def console_n(n, debug: false, validate: false)
|
|
97
97
|
require 'debug'
|
98
98
|
graph = PerformanceBuilder.performance.build!(debug: debug, validate: validate)
|
99
99
|
employees = gen_employees(n)
|
100
|
-
result = graph.context(employees).
|
100
|
+
result = graph.context(employees).get('')
|
101
101
|
binding.b
|
102
102
|
end
|
103
103
|
|
data/lib/lazy_graph/builder.rb
CHANGED
@@ -10,11 +10,11 @@ module LazyGraph
|
|
10
10
|
# Cache up to a fixed number of graphs, context and queries
|
11
11
|
BUILD_CACHE_CONFIG = {
|
12
12
|
# Store up to 1000 graphs
|
13
|
-
graph: {size: 1000, cache: {}},
|
13
|
+
graph: {size: ENV.fetch('LAZY_GRAPH_GRAPH_CACHE_MAX_ENTRIES', 1000).to_i, cache: {}},
|
14
14
|
# Store up to 5000 configs
|
15
|
-
context: {size: 5000, cache: {}},
|
15
|
+
context: {size: ENV.fetch('LAZY_GRAPH_CONTEXT_CACHE_MAX_ENTRIES', 5000).to_i, cache: {}},
|
16
16
|
# Store up to 5000 queries
|
17
|
-
query: {size: 5000, cache: {}}
|
17
|
+
query: {size: ENV.fetch('LAZY_GRAPH_QUERY_CACHE_MAX_ENTRIES', 5000).to_i, cache: {}}
|
18
18
|
}.compare_by_identity.freeze
|
19
19
|
|
20
20
|
include DSL
|
@@ -23,10 +23,16 @@ module LazyGraph
|
|
23
23
|
attr_accessor :schema
|
24
24
|
|
25
25
|
def initialize(schema: { type: 'object', properties: {} }) = @schema = schema
|
26
|
-
def build!(debug: false, validate: false) = @schema.to_lazy_graph(debug: debug, validate: validate, helpers: self.class.helper_modules)
|
27
26
|
def context(value, debug: false, validate: false) = build!(debug: debug, validate: validate).context(value)
|
28
27
|
def eval!(context, *value, debug: false, validate: false) = context(context, validate: validate, debug: debug).query(*value)
|
29
28
|
|
29
|
+
def build!(debug: false, validate: false) = @schema.to_lazy_graph(
|
30
|
+
debug: debug,
|
31
|
+
validate: validate,
|
32
|
+
helpers: self.class.helper_modules,
|
33
|
+
namespace: self.class
|
34
|
+
)
|
35
|
+
|
30
36
|
def self.rules_module(name, schema = { type: 'object', properties: {} }, &blk)
|
31
37
|
rules_modules[:properties][name.to_sym] = { type: :object, properties: schema }
|
32
38
|
module_body_func_name = :"_#{name}"
|
@@ -106,14 +112,15 @@ module LazyGraph
|
|
106
112
|
evaluate_context(builder, context, debug: debug, validate: validate)
|
107
113
|
end
|
108
114
|
|
109
|
-
return context_result if context_result.is_a?(Hash) && context_result[:type]
|
115
|
+
return context_result if context_result.is_a?(Hash) && context_result[:type].equal?(:error)
|
110
116
|
|
111
|
-
|
117
|
+
cache_as(:query, [context_result, query]) do
|
118
|
+
HashUtils.strip_missing({
|
119
|
+
type: :success,
|
120
|
+
result: context_result.resolve(*(query || ''))
|
121
|
+
})
|
122
|
+
end
|
112
123
|
|
113
|
-
{
|
114
|
-
type: :success,
|
115
|
-
result: query_result
|
116
|
-
}
|
117
124
|
rescue SystemStackError => e
|
118
125
|
LazyGraph.logger.error(e.message)
|
119
126
|
LazyGraph.logger.error(e.backtrace.join("\n"))
|
@@ -157,14 +164,14 @@ module LazyGraph
|
|
157
164
|
end
|
158
165
|
rescue ArgumentError => e
|
159
166
|
format_error_response('Invalid Module Argument', e.message)
|
160
|
-
LazyGraph.logger.error(e.backtrace)
|
167
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
161
168
|
end
|
162
169
|
|
163
170
|
private_class_method def self.evaluate_context(builder, context, debug: false, validate: false)
|
164
171
|
builder.context(context, debug: debug, validate: validate)
|
165
172
|
rescue ArgumentError => e
|
166
173
|
format_error_response('Invalid Context Input', e.message)
|
167
|
-
LazyGraph.logger.error(e.backtrace)
|
174
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
168
175
|
end
|
169
176
|
end
|
170
177
|
end
|
@@ -29,10 +29,12 @@ module LazyGraph
|
|
29
29
|
)
|
30
30
|
end
|
31
31
|
|
32
|
-
base.define_singleton_method(:reload_lazy_graphs!) do
|
33
|
-
|
34
|
-
builder
|
35
|
-
|
32
|
+
base.define_singleton_method(:reload_lazy_graphs!) do |clear_previous: false|
|
33
|
+
if clear_previous
|
34
|
+
each_builder do |builder|
|
35
|
+
builder.clear_rules_modules!
|
36
|
+
builder.clear_helper_modules!
|
37
|
+
end
|
36
38
|
end
|
37
39
|
|
38
40
|
reload_paths.flat_map { |p| Dir[p] }.each do |file|
|
@@ -42,13 +44,13 @@ module LazyGraph
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
|
-
base.reload_lazy_graphs!
|
47
|
+
base.reload_lazy_graphs! if reload_paths.any?
|
46
48
|
|
47
49
|
return unless listen
|
48
50
|
|
49
51
|
require 'listen'
|
50
52
|
Listen.to(*reload_paths.map { |p| p.gsub(%r{(?:/\*\*)*/\*\.rb}, '') }) do
|
51
|
-
base.reload_lazy_graphs!
|
53
|
+
base.reload_lazy_graphs!(clear_previous: true)
|
52
54
|
end.start
|
53
55
|
end
|
54
56
|
end
|
data/lib/lazy_graph/context.rb
CHANGED
@@ -11,17 +11,34 @@ module LazyGraph
|
|
11
11
|
graph.validate!(input) if [true, 'input'].include?(graph.validate)
|
12
12
|
@graph = graph
|
13
13
|
@input = input
|
14
|
+
@graph.root_node.properties.each_key do |key|
|
15
|
+
define_singleton_method(key) { get(key) }
|
16
|
+
end
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
19
|
+
def get_json(path)
|
20
|
+
HashUtils.strip_missing(get(path))
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(path)
|
24
|
+
result = resolve(path)
|
25
|
+
raise AbortError, result[:err], cause: result[:error] if result[:err]
|
26
|
+
|
27
|
+
result[:output]
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug(path)
|
31
|
+
result = resolve(path)
|
32
|
+
raise AbortError, result[:err], cause: result[:error] if result[:err]
|
33
|
+
|
34
|
+
result[:debug_trace]
|
18
35
|
end
|
19
36
|
|
20
37
|
def resolve(path)
|
21
38
|
@input = @graph.root_node.fetch_item({ input: @input }, :input, nil)
|
22
39
|
|
23
|
-
query = PathParser.parse(path, true)
|
24
|
-
stack = StackPointer.new(nil, @input, 0, :'$', nil)
|
40
|
+
query = PathParser.parse(path, strip_root: true)
|
41
|
+
stack = StackPointer.new(nil, @input, 0, 0, :'$', nil)
|
25
42
|
stack.root = stack
|
26
43
|
|
27
44
|
result = @graph.root_node.resolve(query, stack)
|
@@ -32,18 +49,18 @@ module LazyGraph
|
|
32
49
|
stack.frame[:DEBUG] = nil
|
33
50
|
end
|
34
51
|
{
|
35
|
-
output:
|
36
|
-
debug_trace:
|
52
|
+
output: result,
|
53
|
+
debug_trace: debug_trace
|
37
54
|
}
|
38
55
|
rescue LazyGraph::AbortError => e
|
39
56
|
{
|
40
|
-
output:
|
57
|
+
output: nil, err: e.message, status: :abort, error: e
|
41
58
|
}
|
42
59
|
rescue StandardError => e
|
43
60
|
LazyGraph.logger.error(e.message)
|
44
|
-
LazyGraph.logger.error(e.backtrace)
|
61
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
45
62
|
{
|
46
|
-
output:
|
63
|
+
output: nil, err: e.message, backtrace: e.backtrace, error: e
|
47
64
|
}
|
48
65
|
end
|
49
66
|
|
@@ -54,9 +71,13 @@ module LazyGraph
|
|
54
71
|
q.text 'graph='
|
55
72
|
q.pp(@graph)
|
56
73
|
end
|
74
|
+
q.group do
|
75
|
+
q.text 'input='
|
76
|
+
q.pp(@input)
|
77
|
+
end
|
57
78
|
end
|
58
79
|
end
|
59
80
|
|
60
|
-
alias []
|
81
|
+
alias [] get
|
61
82
|
end
|
62
83
|
end
|
data/lib/lazy_graph/graph.rb
CHANGED
@@ -13,7 +13,7 @@ module LazyGraph
|
|
13
13
|
def context(input) = Context.new(self, input)
|
14
14
|
def debug? = @debug
|
15
15
|
|
16
|
-
def initialize(input_schema, debug: false, validate: true, helpers: nil)
|
16
|
+
def initialize(input_schema, debug: false, validate: true, helpers: nil, namespace: nil)
|
17
17
|
@json_schema = HashUtils.deep_dup(input_schema, symbolize: true, signature: signature = [0]).merge(type: :object)
|
18
18
|
@debug = debug
|
19
19
|
@validate = validate
|
@@ -25,18 +25,20 @@ module LazyGraph
|
|
25
25
|
raise ArgumentError, 'Root schema must be a non-empty object'
|
26
26
|
end
|
27
27
|
|
28
|
-
@root_node = build_node(@json_schema)
|
28
|
+
@root_node = build_node(@json_schema, namespace: namespace)
|
29
|
+
@root_node.build_derived_inputs!
|
29
30
|
end
|
30
31
|
|
31
|
-
def build_node(schema, path = :'$', name = :root, parent = nil)
|
32
|
+
def build_node(schema, path = :'$', name = :root, parent = nil, namespace: nil)
|
32
33
|
schema[:type] = schema[:type].to_sym
|
33
|
-
node =
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
node = \
|
35
|
+
case schema[:type]
|
36
|
+
when :object then ObjectNode
|
37
|
+
when :array then ArrayNode
|
38
|
+
else Node
|
39
|
+
end.new(name, path, schema, parent, debug: @debug, helpers: @helpers, namespace: namespace)
|
38
40
|
|
39
|
-
if node.type
|
41
|
+
if node.type.equal?(:object)
|
40
42
|
node.children = \
|
41
43
|
{
|
42
44
|
properties: schema.fetch(:properties, {}).map do |key, value|
|
@@ -46,7 +48,7 @@ module LazyGraph
|
|
46
48
|
[Regexp.new(key.to_s), build_node(value, :"#{path}.#{key}", :'<property>', node)]
|
47
49
|
end
|
48
50
|
}
|
49
|
-
elsif node.type
|
51
|
+
elsif node.type.equal?(:array)
|
50
52
|
node.children = build_node(schema.fetch(:items, {}), :"#{path}[]", :items, node)
|
51
53
|
end
|
52
54
|
node
|
@@ -54,6 +56,18 @@ module LazyGraph
|
|
54
56
|
|
55
57
|
def validate!(input, schema = @json_schema)
|
56
58
|
JSON::Validator.validate!(schema, input)
|
59
|
+
rescue JSON::Schema::ValidationError => e
|
60
|
+
raise AbortError, "Input validation failed: #{e.message}", cause: e
|
61
|
+
end
|
62
|
+
|
63
|
+
def pretty_print(q)
|
64
|
+
# Start the custom pretty print
|
65
|
+
q.group(1, '<LazyGraph::Graph ', '>') do
|
66
|
+
q.group do
|
67
|
+
q.text 'props='
|
68
|
+
q.text root_node.children[:properties].keys
|
69
|
+
end
|
70
|
+
end
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
@@ -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
|
@@ -56,17 +58,17 @@ module LazyGraph
|
|
56
58
|
obj.each_with_object({}) do |(key, value), obj|
|
57
59
|
next if value.is_a?(MissingValue)
|
58
60
|
|
59
|
-
obj[key] =
|
61
|
+
obj[key] = strip_missing(value, parent_list)
|
60
62
|
end
|
61
63
|
when Struct
|
62
64
|
obj.members.each_with_object({}) do |key, res|
|
63
65
|
next if obj[key].is_a?(MissingValue)
|
64
66
|
next if obj.invisible.include?(key)
|
65
67
|
|
66
|
-
res[key] =
|
68
|
+
res[key] = strip_missing(obj[key], parent_list)
|
67
69
|
end
|
68
70
|
when Array
|
69
|
-
obj.map { |value|
|
71
|
+
obj.map { |value| strip_missing(value, parent_list) }
|
70
72
|
when MissingValue
|
71
73
|
nil
|
72
74
|
else
|
@@ -8,12 +8,11 @@ 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
|
18
17
|
|
19
18
|
def method_missing(method, *args, &block)
|
@@ -16,40 +16,35 @@ module LazyGraph
|
|
16
16
|
)
|
17
17
|
input = stack_memory.frame
|
18
18
|
@visited[input.object_id >> 2 ^ path.shifted_id] ||= begin
|
19
|
+
path_next = path.next
|
19
20
|
if (path_segment = path.segment).is_a?(PathParser::PathGroup)
|
20
21
|
unless path_segment.index?
|
21
22
|
return input.length.times.map do |index|
|
22
|
-
|
23
|
-
children.resolve(path, stack_memory.push(item, index))
|
23
|
+
children.fetch_and_resolve(path, input, index, stack_memory)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
return resolve(path_segment.options.first.merge(
|
27
|
+
return resolve(path_segment.options.first.merge(path_next), stack_memory, nil) if path_segment.options.one?
|
28
28
|
|
29
|
-
return path_segment.options.map { |part| resolve(part.merge(
|
29
|
+
return path_segment.options.map { |part| resolve(part.merge(path_next), stack_memory, nil) }
|
30
30
|
end
|
31
31
|
|
32
32
|
segment = path_segment&.part
|
33
33
|
case segment
|
34
34
|
when nil
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
|
36
|
+
unless @children.simple?
|
37
|
+
input.length.times do |index|
|
38
|
+
@children.fetch_and_resolve(path, input, index, stack_memory)
|
39
|
+
end
|
38
40
|
end
|
39
41
|
input
|
40
42
|
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
|
43
|
+
@children.fetch_and_resolve(path_next, input, segment.to_s.to_i, stack_memory)
|
48
44
|
else
|
49
45
|
if @child_properties&.key?(segment) || input&.first&.key?(segment)
|
50
46
|
input.length.times.map do |index|
|
51
|
-
|
52
|
-
@children.resolve(path, stack_memory.push(item, index))
|
47
|
+
@children.fetch_and_resolve(path, input, index, stack_memory)
|
53
48
|
end
|
54
49
|
else
|
55
50
|
MissingValue()
|
@@ -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)
|
@@ -124,7 +125,7 @@ module LazyGraph
|
|
124
125
|
@derived_proc_cache[source_location]
|
125
126
|
rescue StandardError => e
|
126
127
|
LazyGraph.logger.error(e.message)
|
127
|
-
LazyGraph.logger.error(e.backtrace)
|
128
|
+
LazyGraph.logger.error(e.backtrace.join("\n"))
|
128
129
|
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
130
|
end
|
130
131
|
|
@@ -167,9 +168,8 @@ module LazyGraph
|
|
167
168
|
|
168
169
|
def parse_rule_string(derived)
|
169
170
|
calc_str = derived[:calc]
|
170
|
-
src = @src
|
171
171
|
instance_eval(
|
172
|
-
"->{ begin; #{calc_str}; rescue StandardError => e; LazyGraph.logger.error(\"Exception in \#{
|
172
|
+
"->{ begin; #{calc_str}; rescue StandardError => e; LazyGraph.logger.error(\"Exception in \#{calc_str}. \#{e.message}\"); LazyGraph.logger.error(e.backtrace.join(\"\\n\")); raise; end }", __FILE__, __LINE__
|
173
173
|
)
|
174
174
|
rescue SyntaxError
|
175
175
|
missing_value = MissingValue { "Syntax error in #{derived[:src]}" }
|
@@ -194,12 +194,25 @@ module LazyGraph
|
|
194
194
|
end.new
|
195
195
|
end
|
196
196
|
|
197
|
+
def resolver_for(path)
|
198
|
+
segment = path.segment.part
|
199
|
+
return root.properties[path.next.segment.part] if segment == :'$'
|
200
|
+
|
201
|
+
(segment == name ? parent.parent : @parent).find_resolver_for(segment)
|
202
|
+
end
|
203
|
+
|
197
204
|
def map_derived_inputs_to_paths(inputs)
|
198
205
|
inputs.values.map.with_index do |path, idx|
|
199
|
-
|
200
|
-
segment.is_a?(PathParser::PathGroup) &&
|
206
|
+
segments = path.parts.map.with_index do |segment, i|
|
207
|
+
if segment.is_a?(PathParser::PathGroup) &&
|
208
|
+
segment.options.length == 1 &&
|
209
|
+
(resolver = resolver_for(segment.options.first))
|
210
|
+
[i, resolver]
|
211
|
+
else
|
212
|
+
nil
|
213
|
+
end
|
201
214
|
end.compact
|
202
|
-
[path, idx,
|
215
|
+
[path, resolver_for(path), idx, segments.any? ? segments : nil]
|
203
216
|
end
|
204
217
|
end
|
205
218
|
end
|
@@ -28,6 +28,17 @@ module LazyGraph
|
|
28
28
|
to_h
|
29
29
|
end
|
30
30
|
|
31
|
+
def to_h
|
32
|
+
HashUtils.strip_missing(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
return super if other.is_a?(self.class)
|
37
|
+
return to_h.eql?(other.to_h) if other.respond_to?(:to_h)
|
38
|
+
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
31
42
|
define_method(:each_key, &members.method(:each))
|
32
43
|
|
33
44
|
def dup
|
@@ -42,17 +53,18 @@ module LazyGraph
|
|
42
53
|
end
|
43
54
|
|
44
55
|
def pretty_print(q)
|
45
|
-
q.group(1, '<
|
56
|
+
q.group(1, '<', '>') do
|
57
|
+
q.text "#{self.class.name} "
|
46
58
|
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
|
59
|
+
m == :DEBUG || v.nil? || v.is_a?(MissingValue)
|
60
|
+
end.to_h) do |k, v|
|
61
|
+
q.text "#{k}: "
|
62
|
+
q.pp v
|
53
63
|
end
|
54
64
|
end
|
55
65
|
end
|
66
|
+
|
67
|
+
alias_method :keys, :members
|
56
68
|
end
|
57
69
|
end
|
58
70
|
end
|
@@ -2,6 +2,8 @@ module LazyGraph
|
|
2
2
|
class ObjectNode < Node
|
3
3
|
require_relative 'symbol_hash'
|
4
4
|
|
5
|
+
attr_reader :properties
|
6
|
+
|
5
7
|
# An object supports the following types of path resolutions.
|
6
8
|
# 1. Property name: obj.property => value
|
7
9
|
# 2. Property name group: obj[property1, property2] => { property1: value1, property2: value2 }
|
@@ -14,58 +16,39 @@ module LazyGraph
|
|
14
16
|
)
|
15
17
|
input = stack_memory.frame
|
16
18
|
|
17
|
-
@visited[input.object_id >> 2 ^ path.shifted_id] ||= begin
|
19
|
+
@visited[(input.object_id >> 2 ^ path.shifted_id) + preserve_keys.object_id] ||= begin
|
18
20
|
return input if input.is_a?(MissingValue)
|
19
21
|
|
22
|
+
path_next = path.next
|
23
|
+
|
20
24
|
if (path_segment = path.segment).is_a?(PathParser::PathGroup)
|
21
25
|
return path_segment.options.each_with_object({}.tap(&:compare_by_identity)) do |part, object|
|
22
|
-
resolve(part.merge(
|
26
|
+
resolve(part.merge(path_next), stack_memory, nil, preserve_keys: object)
|
23
27
|
end
|
24
28
|
end
|
25
|
-
|
26
29
|
if !segment = path_segment&.part
|
27
|
-
@
|
28
|
-
|
29
|
-
node.resolve(path.next, stack_memory.push(item, key))
|
30
|
+
@complex_properties_a.each do |key, node|
|
31
|
+
node.fetch_and_resolve(path_next, input, key, stack_memory)
|
30
32
|
end
|
31
|
-
if @
|
33
|
+
if @complex_pattern_properties_a.any?
|
32
34
|
input.each_key do |key|
|
33
|
-
node = !@properties[key] && @
|
34
|
-
|
35
|
-
|
35
|
+
node = !@properties[key] && @complex_pattern_properties_a.find do |(pattern, _value)|
|
36
|
+
pattern.match?(key)
|
37
|
+
end&.last
|
38
|
+
next unless node
|
39
|
+
|
40
|
+
node.fetch_and_resolve(path_next, input, key, stack_memory)
|
36
41
|
end
|
37
42
|
end
|
38
43
|
input
|
39
44
|
elsif (prop = @properties[segment])
|
40
|
-
|
41
|
-
value = prop.resolve(
|
42
|
-
path.next, stack_memory.push(item, segment)
|
43
|
-
)
|
44
|
-
preserve_keys ? preserve_keys[segment] = value : value
|
45
|
-
elsif segment == :*
|
46
|
-
# rubocop:disable
|
47
|
-
(input.keys | @properties_a.map(&:first)).each do |key|
|
48
|
-
next unless (node = @properties[key] || @pattern_properties.find do |(pattern, _value)|
|
49
|
-
pattern.match?(key)
|
50
|
-
end&.last)
|
51
|
-
|
52
|
-
item = node.fetch_item(input, key, stack_memory)
|
53
|
-
preserve_keys[key] = node.resolve(path.next, stack_memory.push(item, key))
|
54
|
-
end
|
45
|
+
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
55
46
|
elsif (_, prop = @pattern_properties.find { |(key, _val)| key.match?(segment) })
|
56
|
-
|
57
|
-
value = prop.resolve(
|
58
|
-
path.next, stack_memory.push(item, segment)
|
59
|
-
)
|
60
|
-
preserve_keys ? preserve_keys[segment] = value : value
|
47
|
+
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
61
48
|
elsif input.key?(segment)
|
62
49
|
prop = @properties[segment] = lazy_init_node!(input[segment], segment)
|
63
50
|
@properties_a = @properties.to_a
|
64
|
-
|
65
|
-
value = prop.resolve(
|
66
|
-
path.next, stack_memory.push(item, segment)
|
67
|
-
)
|
68
|
-
preserve_keys ? preserve_keys[segment] = value : value
|
51
|
+
prop.fetch_and_resolve(path_next, input, segment, stack_memory, preserve_keys)
|
69
52
|
else
|
70
53
|
value = MissingValue()
|
71
54
|
preserve_keys ? preserve_keys[segment] = value : value
|
@@ -76,10 +59,10 @@ module LazyGraph
|
|
76
59
|
end
|
77
60
|
|
78
61
|
def find_resolver_for(segment)
|
79
|
-
if segment
|
62
|
+
if segment.equal?(:'$')
|
80
63
|
root
|
81
64
|
elsif @properties.key?(segment)
|
82
|
-
|
65
|
+
@properties[segment]
|
83
66
|
else
|
84
67
|
@parent&.find_resolver_for(segment)
|
85
68
|
end
|
@@ -88,32 +71,30 @@ module LazyGraph
|
|
88
71
|
def children=(value)
|
89
72
|
@children = value
|
90
73
|
|
91
|
-
@properties = @children.fetch(:properties, {})
|
92
|
-
@properties.compare_by_identity
|
74
|
+
@properties = @children.fetch(:properties, {}).compare_by_identity
|
93
75
|
@pattern_properties = @children.fetch(:pattern_properties, [])
|
94
76
|
|
95
|
-
@
|
77
|
+
@complex_properties_a = @properties.to_a.reject { _2.simple? }
|
78
|
+
@complex_pattern_properties_a = @pattern_properties.reject { _2.simple? }
|
96
79
|
|
97
80
|
@has_properties = @properties.any? || @pattern_properties.any?
|
98
81
|
|
99
|
-
return unless @
|
82
|
+
return unless @has_properties
|
100
83
|
|
101
84
|
if @pattern_properties.any?
|
102
85
|
@property_class = SymbolHash
|
103
86
|
else
|
104
87
|
invisible = @properties.select { |_k, v| v.invisible }.map(&:first)
|
105
|
-
@property_class =
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
private def build_caster
|
112
|
-
if @property_class
|
113
|
-
->(value) { value.is_a?(@property_class) ? value : @property_class.new(value.to_h) }
|
114
|
-
else
|
115
|
-
->(value) { value }
|
88
|
+
@property_class = LazyGraph.fetch_property_class(
|
89
|
+
path,
|
90
|
+
{ members: @properties.keys + (@debug && !parent ? [:DEBUG] : []),
|
91
|
+
invisible: invisible },
|
92
|
+
namespace: root.namespace
|
93
|
+
)
|
116
94
|
end
|
95
|
+
define_singleton_method(:cast, lambda { |val|
|
96
|
+
val.is_a?(@property_class) ? val : @property_class.new(val.to_h)
|
97
|
+
})
|
117
98
|
end
|
118
99
|
end
|
119
100
|
end
|
data/lib/lazy_graph/node.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'debug'
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'bigdecimal/util'
|
@@ -12,8 +11,19 @@ module LazyGraph
|
|
12
11
|
|
13
12
|
DIGIT_REGEXP = /^-?\d+$/
|
14
13
|
SAFE_TOKEN_REGEXP = /^[A-Za-z][A-Za-z0-9]*$/
|
15
|
-
PROPERTY_CLASSES =
|
16
|
-
|
14
|
+
PROPERTY_CLASSES = {}
|
15
|
+
UNIQUE_NAME_COUNTER = Hash.new(0)
|
16
|
+
|
17
|
+
def self.fetch_property_class(name, members, namespace: nil)
|
18
|
+
namespace ||= LazyGraph
|
19
|
+
PROPERTY_CLASSES[members] ||= begin
|
20
|
+
name = name.to_s.capitalize.gsub(/(\.|_)([a-zA-Z])/) do |m|
|
21
|
+
m[1].upcase
|
22
|
+
end.gsub(/[^a-zA-Z]/, '').then { |n| n.length > 0 ? n : 'NodeProps' }
|
23
|
+
index = UNIQUE_NAME_COUNTER[[name, namespace]] += 1
|
24
|
+
full_name = "#{name}#{index > 1 ? index : ''}"
|
25
|
+
namespace.const_set(full_name, NodeProperties.build(**members))
|
26
|
+
end
|
17
27
|
end
|
18
28
|
|
19
29
|
# Class: Node
|
@@ -39,27 +49,44 @@ module LazyGraph
|
|
39
49
|
include DerivedRules
|
40
50
|
attr_accessor :name, :path, :type, :derived, :depth, :parent, :root, :invisible
|
41
51
|
attr_accessor :children
|
42
|
-
attr_reader :is_object
|
52
|
+
attr_reader :is_object, :namespace
|
53
|
+
|
54
|
+
def simple? = @simple
|
43
55
|
|
44
|
-
def initialize(name, path, node, parent, debug: false, helpers: nil)
|
56
|
+
def initialize(name, path, node, parent, debug: false, helpers: nil, namespace: nil)
|
45
57
|
@name = name
|
46
58
|
@path = path
|
47
59
|
@parent = parent
|
48
60
|
@debug = debug
|
49
61
|
@depth = parent ? parent.depth + 1 : 0
|
50
62
|
@root = parent ? parent.root : self
|
63
|
+
@rule = node[:rule]
|
51
64
|
@type = node[:type]
|
65
|
+
@helpers = helpers
|
52
66
|
@invisible = debug ? false : node[:invisible]
|
53
67
|
@visited = {}.compare_by_identity
|
68
|
+
@namespace = namespace
|
69
|
+
|
54
70
|
instance_variable_set("@is_#{@type}", true)
|
55
71
|
define_singleton_method(:cast, build_caster)
|
56
72
|
define_missing_value_proc!
|
57
73
|
|
58
74
|
@has_default = node.key?(:default)
|
59
75
|
@default = @has_default ? cast(node[:default]) : MissingValue { @name }
|
60
|
-
@resolution_stack = []
|
61
76
|
|
62
|
-
|
77
|
+
# Simple nodes are not a container type, and do not have rule or default
|
78
|
+
@simple = !(%i[object array date time timestamp decimal].include?(@type) || node[:rule] || @has_default)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_derived_inputs!
|
82
|
+
build_derived_inputs(@rule, @helpers) if @rule
|
83
|
+
return unless @children
|
84
|
+
return @children.build_derived_inputs! if @children.is_a?(Node)
|
85
|
+
|
86
|
+
@children[:properties]&.each_value(&:build_derived_inputs!)
|
87
|
+
@children[:pattern_properties]&.each do |(_, node)|
|
88
|
+
node.build_derived_inputs!
|
89
|
+
end
|
63
90
|
end
|
64
91
|
|
65
92
|
def define_missing_value_proc!
|
@@ -69,7 +96,18 @@ module LazyGraph
|
|
69
96
|
)
|
70
97
|
end
|
71
98
|
|
72
|
-
|
99
|
+
def fetch_and_resolve(path, input, segment, stack_memory, preserve_keys = nil, recurse: false)
|
100
|
+
item = fetch_item(input, segment, stack_memory)
|
101
|
+
unless @simple || item.is_a?(MissingValue)
|
102
|
+
item = resolve(
|
103
|
+
path,
|
104
|
+
stack_memory.push(item, segment)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
preserve_keys ? preserve_keys[segment] = item : item
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_caster
|
73
111
|
if @is_decimal
|
74
112
|
->(value) { value.is_a?(BigDecimal) ? value : value.to_d }
|
75
113
|
elsif @is_date
|
@@ -100,9 +138,9 @@ module LazyGraph
|
|
100
138
|
|
101
139
|
def clear_visits!
|
102
140
|
@visited.clear
|
103
|
-
@resolution_stack
|
104
|
-
@path_cache
|
105
|
-
@resolvers
|
141
|
+
@resolution_stack&.clear
|
142
|
+
@path_cache&.clear
|
143
|
+
@resolvers&.clear
|
106
144
|
|
107
145
|
return unless @children
|
108
146
|
return @children.clear_visits! if @children.is_a?(Node)
|
@@ -113,10 +151,6 @@ module LazyGraph
|
|
113
151
|
end
|
114
152
|
end
|
115
153
|
|
116
|
-
# When we assign children to a node, we preemptively extract the properties, and pattern properties
|
117
|
-
# in both hash and array form. This micro-optimization pays off when we resolve values in the graph at
|
118
|
-
# very high frequency.
|
119
|
-
|
120
154
|
def resolve(
|
121
155
|
path,
|
122
156
|
stack_memory,
|
@@ -142,7 +176,7 @@ module LazyGraph
|
|
142
176
|
when Array then :array
|
143
177
|
end
|
144
178
|
node.children = Node.new(:items, :"#{path}.#{key}[].items", { type: child_type }, node)
|
145
|
-
node.children.children = { properties: {}, pattern_properties: [] } if child_type
|
179
|
+
node.children.children = { properties: {}, pattern_properties: [] } if child_type.equal? :object
|
146
180
|
node
|
147
181
|
else
|
148
182
|
Node.new(key, :"#{path}.#{key}", {}, self)
|
@@ -161,43 +195,24 @@ module LazyGraph
|
|
161
195
|
end
|
162
196
|
end
|
163
197
|
|
164
|
-
def resolve_input(stack_memory, path, key)
|
165
|
-
input_id = key.object_id >> 2 ^ stack_memory.shifted_id
|
166
|
-
if @resolution_stack.include?(input_id)
|
167
|
-
if @debug
|
168
|
-
stack_memory.log_debug(
|
169
|
-
property: "#{stack_memory}.#{key}",
|
170
|
-
exception: 'Infinite Recursion Detected during dependency resolution'
|
171
|
-
)
|
172
|
-
end
|
173
|
-
return MissingValue { "Infinite Recursion in #{stack_memory} => #{path.to_path_str}" }
|
174
|
-
end
|
175
|
-
|
176
|
-
@resolution_stack << (input_id)
|
177
|
-
first_segment = path.segment.part
|
178
|
-
|
179
|
-
resolver_node = @resolvers[first_segment] ||= (first_segment == key ? parent.parent : @parent).find_resolver_for(first_segment)
|
180
|
-
|
181
|
-
if resolver_node
|
182
|
-
input_frame_pointer = stack_memory.ptr_at(resolver_node.depth)
|
183
|
-
resolver_node.resolve(
|
184
|
-
first_segment == :'$' ? path.next : path,
|
185
|
-
input_frame_pointer,
|
186
|
-
nil
|
187
|
-
)
|
188
|
-
else
|
189
|
-
MissingValue { path.to_path_str }
|
190
|
-
end
|
191
|
-
ensure
|
192
|
-
@resolution_stack.pop
|
193
|
-
end
|
194
|
-
|
195
198
|
def ancestors
|
196
199
|
@ancestors ||= [self, *(@parent ? @parent.ancestors : [])]
|
197
200
|
end
|
198
201
|
|
199
202
|
def find_resolver_for(segment)
|
200
|
-
segment
|
203
|
+
segment.equal?(:'$') ? root : @parent&.find_resolver_for(segment)
|
204
|
+
end
|
205
|
+
|
206
|
+
def resolve_relative_input(stack_memory, path)
|
207
|
+
input_frame_pointer = path.absolute? ? stack_memory.root : stack_memory.ptr_at(depth - 1)
|
208
|
+
input_frame_pointer.recursion_depth += 1
|
209
|
+
return input_frame_pointer.frame[path.first_path_segment.part] if @simple
|
210
|
+
|
211
|
+
fetch_and_resolve(
|
212
|
+
path.absolute? ? path.next.next : path.next, input_frame_pointer.frame, path.first_path_segment.part, input_frame_pointer
|
213
|
+
)
|
214
|
+
ensure
|
215
|
+
input_frame_pointer.recursion_depth -= 1
|
201
216
|
end
|
202
217
|
|
203
218
|
def fetch_item(input, key, stack)
|
@@ -217,20 +232,34 @@ module LazyGraph
|
|
217
232
|
|
218
233
|
return input[key] = @default unless derived
|
219
234
|
|
220
|
-
if
|
221
|
-
|
222
|
-
|
223
|
-
|
235
|
+
if stack.recursion_depth >= 8
|
236
|
+
input_id = key.object_id >> 2 ^ input.object_id << 28
|
237
|
+
if @resolution_stack.key?(input_id)
|
238
|
+
if @debug
|
239
|
+
stack.log_debug(
|
240
|
+
output: :"#{stack}.#{key}",
|
241
|
+
exception: 'Infinite Recursion Detected during dependency resolution'
|
242
|
+
)
|
243
|
+
end
|
244
|
+
return MissingValue { "Infinite Recursion in #{stack} => #{key}" }
|
245
|
+
end
|
246
|
+
@resolution_stack[input_id] = true
|
224
247
|
end
|
248
|
+
|
249
|
+
@copy_input ? copy_item!(input, key, stack, @inputs.first) : derive_item!(input, key, stack)
|
250
|
+
ensure
|
251
|
+
@resolution_stack.delete(input_id) if input_id
|
225
252
|
end
|
226
253
|
|
227
|
-
def copy_item!(input, key, stack, (path, _i,
|
228
|
-
|
229
|
-
|
254
|
+
def copy_item!(input, key, stack, (path, resolver, _i, segments))
|
255
|
+
missing_value = resolver ? nil : MissingValue { key }
|
256
|
+
if resolver && segments
|
230
257
|
parts = path.parts.dup
|
231
258
|
parts_identity = path.identity
|
232
|
-
|
233
|
-
|
259
|
+
segments.each do |index, resolver|
|
260
|
+
break missing_value = MissingValue { key } unless resolver
|
261
|
+
|
262
|
+
part = resolver.resolve_relative_input(stack, parts[index].options.first)
|
234
263
|
break missing_value = part if part.is_a?(MissingValue)
|
235
264
|
|
236
265
|
part_sym = part.to_s.to_sym
|
@@ -240,12 +269,12 @@ module LazyGraph
|
|
240
269
|
path = @path_cache[parts_identity] ||= PathParser::Path.new(parts: parts) unless missing_value
|
241
270
|
end
|
242
271
|
|
243
|
-
result = missing_value || cast(
|
272
|
+
result = missing_value || cast(resolver.resolve_relative_input(stack, path))
|
244
273
|
|
245
274
|
if @debug
|
246
275
|
stack.log_debug(
|
247
276
|
output: :"#{stack}.#{key}",
|
248
|
-
result: result,
|
277
|
+
result: HashUtils.deep_dup(result),
|
249
278
|
inputs: @node_context.to_h.except(:itself, :stack_ptr),
|
250
279
|
calc: @src
|
251
280
|
)
|
@@ -254,13 +283,15 @@ module LazyGraph
|
|
254
283
|
end
|
255
284
|
|
256
285
|
def derive_item!(input, key, stack)
|
257
|
-
@inputs.each do |path, i,
|
258
|
-
if
|
286
|
+
@inputs.each do |path, resolver, i, segments|
|
287
|
+
if segments
|
259
288
|
missing_value = nil
|
260
289
|
parts = path.parts.dup
|
261
290
|
parts_identity = path.identity
|
262
|
-
|
263
|
-
|
291
|
+
segments.each do |index, resolver|
|
292
|
+
break missing_value = MissingValue { key } unless resolver
|
293
|
+
|
294
|
+
part = resolver.resolve_relative_input(stack, parts[index].options.first)
|
264
295
|
break missing_value = part if part.is_a?(MissingValue)
|
265
296
|
|
266
297
|
part_sym = part.to_s.to_sym
|
@@ -269,7 +300,7 @@ module LazyGraph
|
|
269
300
|
end
|
270
301
|
path = @path_cache[parts_identity] ||= PathParser::Path.new(parts: parts) unless missing_value
|
271
302
|
end
|
272
|
-
result = missing_value ||
|
303
|
+
result = missing_value || resolver.resolve_relative_input(stack, path)
|
273
304
|
@node_context[i] = result.is_a?(MissingValue) ? nil : result
|
274
305
|
end
|
275
306
|
|
@@ -10,25 +10,33 @@ module LazyGraph
|
|
10
10
|
Path = Struct.new(:parts, keyword_init: true) do
|
11
11
|
def next = @next ||= parts.length <= 1 ? Path::BLANK : Path.new(parts: parts[1..])
|
12
12
|
def empty? = @empty ||= parts.empty?
|
13
|
+
def length = @length ||= parts.length
|
13
14
|
def segment = @segment ||= parts&.[](0)
|
14
|
-
def
|
15
|
+
def absolute? = instance_variable_defined?(:@absolute) ? @absolute : (@absolute = segment&.part.equal?(:'$'))
|
16
|
+
def index? = @index ||= !empty? && segment&.index?
|
15
17
|
def identity = @identity ||= parts&.each_with_index&.reduce(0) { |acc, (p, i)| acc ^ (p.object_id) << (i * 8) }
|
16
18
|
def map(&block) = empty? ? self : Path.new(parts: parts.map(&block))
|
17
19
|
def shifted_id = @shifted_id ||= object_id << 28
|
20
|
+
def first_path_segment = @first_path_segment ||= absolute? ? self.next.segment : segment
|
18
21
|
|
19
22
|
def merge(other)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
23
|
+
if other.empty?
|
24
|
+
self
|
25
|
+
else
|
26
|
+
empty? ? other : Path.new(parts: parts + other.parts)
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def to_path_str
|
29
31
|
@to_path_str ||= create_path_str
|
30
32
|
end
|
31
33
|
|
34
|
+
def ==(other)
|
35
|
+
return parts == other if other.is_a?(Array)
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
32
40
|
private
|
33
41
|
|
34
42
|
def create_path_str
|
@@ -8,6 +8,14 @@ module LazyGraph
|
|
8
8
|
def index?
|
9
9
|
@index ||= part =~ INDEX_REGEXP
|
10
10
|
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
return part == other.to_sym if other.is_a?(String)
|
14
|
+
return part == other if other.is_a?(Symbol)
|
15
|
+
return part == other if other.is_a?(Array)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end
|
@@ -19,7 +19,7 @@ module LazyGraph
|
|
19
19
|
require_relative 'path_parser/path_part'
|
20
20
|
# This module is responsible for parsing complex path strings into structured components.
|
21
21
|
# Public class method to parse the path string
|
22
|
-
def self.parse(path, strip_root
|
22
|
+
def self.parse(path, strip_root: false)
|
23
23
|
return Path::BLANK if path.nil? || path.empty?
|
24
24
|
|
25
25
|
start = strip_root && path.to_s.start_with?('$.') ? 2 : 0
|
data/lib/lazy_graph/server.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rack'
|
4
|
+
|
3
5
|
module LazyGraph
|
4
6
|
class Server
|
5
7
|
ALLOWED_VALUES_VALIDATE = [true, false, nil, 'input', 'context'].to_set.freeze
|
6
8
|
ALLOWED_VALUES_DEBUG = [true, false, nil].to_set.freeze
|
7
9
|
|
10
|
+
attr_reader :routes
|
11
|
+
|
8
12
|
def initialize(routes: {})
|
9
13
|
@routes = routes.transform_keys(&:to_sym).compare_by_identity
|
10
14
|
end
|
@@ -59,6 +63,7 @@ module LazyGraph
|
|
59
63
|
rescue StandardError => e
|
60
64
|
LazyGraph.logger.error(e.message)
|
61
65
|
LazyGraph.logger.error(e.backtrace.join("\n"))
|
66
|
+
|
62
67
|
return error!(request, 500, 'Internal Server Error', e.message)
|
63
68
|
end
|
64
69
|
end
|
@@ -4,35 +4,38 @@ module LazyGraph
|
|
4
4
|
# Module to provide lazy graph functionalities using stack pointers.
|
5
5
|
POINTER_POOL = []
|
6
6
|
|
7
|
-
StackPointer = Struct.new(:parent, :frame, :depth, :key, :root) do
|
7
|
+
StackPointer = Struct.new(:parent, :frame, :depth, :recursion_depth, :key, :root) do
|
8
8
|
attr_accessor :pointer_cache
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# Pushes a new frame onto the stack, creating or reusing a StackPointer.
|
11
|
+
# Frames represent activation contexts; keys are identifiers within those frames.
|
12
12
|
def push(frame, key)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
StackPointer.new(parent: self, frame: frame, key: key, depth: depth + 1, root: root || self)
|
23
|
-
end
|
13
|
+
ptr = POINTER_POOL.pop || StackPointer.new
|
14
|
+
ptr.parent = self
|
15
|
+
ptr.root = root || self
|
16
|
+
ptr.frame = frame
|
17
|
+
ptr.key = key
|
18
|
+
ptr.depth = depth + 1
|
19
|
+
ptr.recursion_depth = recursion_depth
|
20
|
+
ptr.pointer_cache&.clear
|
21
|
+
ptr
|
24
22
|
end
|
25
23
|
|
24
|
+
# Recycles the current StackPointer by adding it back to the pointer pool.
|
25
|
+
# Once recycled, this instance should no longer be used unless reassigned by push.
|
26
26
|
def recycle!
|
27
27
|
POINTER_POOL.push(self)
|
28
28
|
nil
|
29
29
|
end
|
30
30
|
|
31
|
+
# Retrieves the StackPointer at a specific index in the upward chain of parents.
|
31
32
|
def ptr_at(index)
|
32
33
|
@pointer_cache ||= {}.compare_by_identity
|
33
34
|
@pointer_cache[index] ||= depth == index ? self : parent&.ptr_at(index)
|
34
35
|
end
|
35
36
|
|
37
|
+
# Handles method calls not explicitly defined in this class by delegating them
|
38
|
+
# first to the frame, then to the parent, recursively up the stack.
|
36
39
|
def method_missing(name, *args, &block)
|
37
40
|
if frame.respond_to?(name)
|
38
41
|
frame.send(name, *args, &block)
|
@@ -43,20 +46,25 @@ module LazyGraph
|
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
49
|
+
# Returns the key associated with this stack pointer's frame.
|
46
50
|
def index
|
47
51
|
key
|
48
52
|
end
|
49
53
|
|
54
|
+
# Logs debugging information related to this stack pointer in the root frame's DEBUG section.
|
50
55
|
def log_debug(**log_item)
|
51
56
|
root.frame[:DEBUG] = [] if !root.frame[:DEBUG] || root.frame[:DEBUG].is_a?(MissingValue)
|
52
57
|
root.frame[:DEBUG] << { **log_item, location: to_s }
|
53
58
|
nil
|
54
59
|
end
|
55
60
|
|
61
|
+
# Determines if the stack pointer can respond to a missing method by mimicking the behavior
|
62
|
+
# of the frame or any parent stack pointers recursively.
|
56
63
|
def respond_to_missing?(name, include_private = false)
|
57
64
|
frame.respond_to?(name, include_private) || parent.respond_to?(name, include_private)
|
58
65
|
end
|
59
66
|
|
67
|
+
# Returns a string representation of the stacking path of keys up to this pointer.
|
60
68
|
def to_s
|
61
69
|
if parent
|
62
70
|
"#{parent}#{key.to_s =~ /\d+/ ? "[#{key}]" : ".#{key}"}"
|
data/lib/lazy_graph/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lazy_graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2024-12-
|
10
|
+
date: 2024-12-29 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: json-schema
|