lazy_graph 0.1.3 → 0.1.6
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/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
|