lazy_graph 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ module LazyGraph
6
+ class RackApp
7
+ ALLOWED_VALUES_VALIDATE = [true, false, nil, 'input', 'context'].to_set.freeze
8
+ ALLOWED_VALUES_DEBUG = [true, false, nil].to_set.freeze
9
+
10
+ attr_reader :routes
11
+
12
+ def initialize(routes: {})
13
+ @routes = routes.transform_keys(&:to_sym).compare_by_identity
14
+ end
15
+
16
+ def call(env)
17
+ env[:X_REQUEST_TIME_START] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
18
+ request = Rack::Request.new(env)
19
+
20
+ return routes!(request) if (request.path == '/routes' || request.path == '/') && request.get?
21
+ return health!(request) if request.path == '/health' && request.get?
22
+ return not_found!(request) unless (graph_module = @routes[request.path.to_sym])
23
+ return success!(request, graph_module.usage) if request.get?
24
+ return not_found!("#{request.request_method} #{request.path}") unless request.post?
25
+
26
+ query_lazy_graph!(request, graph_module)
27
+ end
28
+
29
+ def query_lazy_graph!(request, graph_module)
30
+ body = begin
31
+ JSON.parse(request.body.read, symbolize_names: true)
32
+ rescue JSON::ParserError => e
33
+ return not_acceptable!(request, 'Invalid JSON', e.message)
34
+ end
35
+ context, modules, validate, debug, query = body.values_at(:context, :modules, :validate, :debug, :query)
36
+ unless context.is_a?(Hash) && !context.empty?
37
+ return not_acceptable!(request, "Invalid 'context' Parameter", 'Should be a non-empty object.')
38
+ end
39
+
40
+ unless (modules.is_a?(Hash) && !modules.empty?) || modules.is_a?(String) || (modules.is_a?(Array) && modules.all? do |m|
41
+ m.is_a?(String)
42
+ end)
43
+ return not_acceptable!(request, "Invalid 'modules' Parameter",
44
+ 'Should be a string, string-array or non-empty object.')
45
+ end
46
+
47
+ modules = Array(modules).map { |m| [m, {}] }.to_h unless modules.is_a?(Hash)
48
+
49
+ unless ALLOWED_VALUES_VALIDATE.include?(validate)
50
+ return not_acceptable!(
51
+ request, "Invalid 'validate' Parameter", "Should be nil, bool, or one of 'input', 'context'"
52
+ )
53
+ end
54
+
55
+ unless ALLOWED_VALUES_DEBUG.include?(debug) || debug.is_a?(String)
56
+ return not_acceptable!(request, "Invalid 'debug' Parameter", 'Should be nil or bool')
57
+ end
58
+
59
+ debug = Regexp.new(Regexp.escape(debug)) if debug.is_a?(String) && debug != 'exceptions'
60
+
61
+ unless query.nil? || query.is_a?(String) || (query.is_a?(Array) && query.all? do |q|
62
+ q.is_a?(String)
63
+ end)
64
+ return not_acceptable!(request, "Invalid 'query' Parameter", 'Should be nil, array or string array')
65
+ end
66
+
67
+ begin
68
+ result = graph_module.eval!(
69
+ modules: modules,
70
+ context: context,
71
+ validate: validate.nil? ? true : validate,
72
+ debug: if !debug.nil?
73
+ debug
74
+ else
75
+ LazyGraph::Environment.development? ? 'exceptions' : debug
76
+ end,
77
+ query: query
78
+ )
79
+ return not_acceptable!(request, result[:message], result[:detail]) if result[:type] == :error
80
+
81
+ success!(request, result)
82
+ rescue AbortError, ValidationError => e
83
+ LazyGraph.logger.error(e.message)
84
+ LazyGraph.logger.error(e.backtrace.join("\n"))
85
+ error!(request, 400, 'Bad Request', e.message)
86
+ rescue StandardError => e
87
+ LazyGraph.logger.error(e.message)
88
+ LazyGraph.logger.error(e.backtrace.join("\n"))
89
+
90
+ error!(request, 500, 'Internal Server Error', e.message)
91
+ end
92
+ end
93
+
94
+ def routes!(request)
95
+ success!(request, @routes.keys.map { |k, _v| { route: k.to_s, methods: %i[GET POST] } })
96
+ end
97
+
98
+ def health!(request)
99
+ success!(request, { status: 'ok' })
100
+ end
101
+
102
+ def not_acceptable!(request, message, details = '')
103
+ error!(request, 406, message, details)
104
+ end
105
+
106
+ def not_found!(request, details = '')
107
+ error!(request, 404, 'Not Found', details)
108
+ end
109
+
110
+ def request_ms(request)
111
+ ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - request.env[:X_REQUEST_TIME_START]) * 1000.0).round(3)
112
+ end
113
+
114
+ def success!(request, result, status: 200)
115
+ req_ms = request_ms(request)
116
+
117
+ LazyGraph.logger.info(
118
+ Logger.build_color_string do
119
+ "#{bold(request.request_method)}: #{dim(request.path)} => #{green(status)} #{light_gray("#{req_ms}ms")}"
120
+ end
121
+ )
122
+
123
+ [status, { 'content-type' => 'application/json' }, [JSON.fast_generate(result)]]
124
+ end
125
+
126
+ def error!(request, status, message, details = '')
127
+ req_ms = request_ms(request)
128
+
129
+ LazyGraph.logger.error(
130
+ Logger.build_color_string do
131
+ "#{bold(request.request_method)}: #{dim(request.path)} => #{status.to_i >= 500 ? red(status) : orange(status)} #{light_gray("#{req_ms}ms")}"
132
+ end
133
+ )
134
+
135
+ [status, { 'content-type' => 'application/json' }, [JSON.fast_generate({ 'error': message, 'details': details })]]
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ module LazyGraph
6
+ class RackServer
7
+ def initialize(app)
8
+ @app = app
9
+ @running = false
10
+ @threads = [] #
11
+ @threads_m = Mutex.new
12
+ end
13
+
14
+ def start(port: 9292, workers: 4)
15
+ trap_signals
16
+
17
+ @port = port
18
+ @workers = workers
19
+
20
+ if workers > 1
21
+ puts "Starting Raxx server with #{workers} processes on port #{port}..."
22
+ @server = TCPServer.new(@port)
23
+ @server.listen(1024)
24
+
25
+ workers.times do
26
+ fork(&method(:run_accept_loop))
27
+ end
28
+ Process.waitall
29
+ else
30
+ puts "Starting single-process server on port #{port}..."
31
+ @server = TCPServer.new(@port)
32
+ @server.listen(1024)
33
+ run_accept_loop
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ #
40
+ # Main accept loop
41
+ #
42
+ def run_accept_loop
43
+ enable_reuse(@server)
44
+ @running = true
45
+ puts "[PID #{Process.pid}] Listening on port #{@port}..."
46
+
47
+ while @running
48
+ begin
49
+ client = @server.accept_nonblock if @running
50
+ rescue IO::WaitReadable, Errno::EINTR
51
+ IO.select([@server], nil, nil, 0.1) if @running
52
+ retry
53
+ end
54
+
55
+ # Handle connection in a new thread
56
+ next unless client
57
+
58
+ thr = Thread.start(client) do |socket|
59
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
60
+ handle_request(socket)
61
+ socket.close
62
+ rescue Errno::ECONNRESET, Errno::EPIPE, IOError, Errno::EINVAL
63
+ # Connection reset by peer - ignore
64
+ ensure
65
+ remove_thread(Thread.current)
66
+ end
67
+
68
+ add_thread(thr)
69
+ end
70
+ ensure
71
+ if @server
72
+ puts "[PID #{Process.pid}] Shutting down server socket..."
73
+ @server.close
74
+ end
75
+ end
76
+
77
+ #
78
+ # Actually handle requests in a keep-alive loop
79
+ #
80
+ def handle_request(socket)
81
+ while @running
82
+
83
+ begin
84
+ request_line = socket.gets if @running
85
+ rescue IO::WaitReadable, Errno::EINTR, Errno::EPIPE
86
+ IO.select([socket], nil, nil, 0.5) if @running
87
+ retry
88
+ end
89
+
90
+ break if request_line.nil? || request_line.strip.empty?
91
+
92
+ method, path, http_version = request_line.split
93
+
94
+ # Parse headers
95
+ headers = {}
96
+ content_length = 0
97
+ while (line = socket.gets) && line != "\r\n"
98
+ key, value = line.split(': ', 2)
99
+ headers[key.downcase] = value.strip
100
+ content_length = value.to_i if key == 'Content-Length'
101
+ end
102
+
103
+ body = content_length.positive? ? socket.read(content_length) : (+'').force_encoding('ASCII-8BIT')
104
+
105
+ # Build Rack environment
106
+ env = {
107
+ 'REQUEST_METHOD' => method,
108
+ 'PATH_INFO' => path,
109
+ 'HTTP_VERSION' => http_version,
110
+ 'QUERY_STRING' => path[/\?.*/].to_s,
111
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
112
+ 'SERVER_NAME' => 'localhost',
113
+ 'rack.url_scheme' => 'http',
114
+ 'rack.request.headers' => headers,
115
+ 'rack.input' => StringIO.new(body),
116
+ 'rack.errors' => StringIO.new('')
117
+ }
118
+
119
+ # Call the Rack app
120
+ status, response_headers, body_enum = @app.call(env)
121
+
122
+ content = body_enum.to_enum(:each).map(&:to_s).join
123
+
124
+ response_headers['Connection'] = 'keep-alive'
125
+ response_headers['Content-Length'] = content.bytesize.to_s
126
+
127
+ # Write the response
128
+ socket.print "HTTP/1.1 #{status} #{http_status_message(status)}\r\n"
129
+ response_headers.each { |k, v| socket.print "#{k}: #{v}\r\n" }
130
+ socket.print "\r\n"
131
+ socket.print content
132
+ end
133
+ end
134
+
135
+ #
136
+ # Thread management helpers
137
+ #
138
+ def add_thread(thr)
139
+ @threads_m.synchronize { @threads << thr }
140
+ end
141
+
142
+ def remove_thread(thr)
143
+ @threads_m.synchronize { @threads.delete(thr) }
144
+ end
145
+
146
+ #
147
+ # Trap signals, set @running = false, then force-kill leftover threads
148
+ #
149
+ def trap_signals
150
+ %w[INT TERM].each do |signal|
151
+ trap(signal) do
152
+ @running = false
153
+ # Give threads a grace period to finish
154
+ graceful_shutdown_threads(timeout: 5)
155
+ end
156
+ end
157
+ end
158
+
159
+ #
160
+ # Gracefully wait for each thread to finish; then force-kill if still alive
161
+ #
162
+ def graceful_shutdown_threads(timeout:)
163
+ @threads.each do |thr|
164
+ next unless thr.alive?
165
+
166
+ # Try a graceful join
167
+ thr.join(timeout)
168
+ # If still running after timeout, forcibly kill
169
+ if thr.alive?
170
+ puts "[PID #{Process.pid}] Force-killing thread #{thr.object_id}"
171
+ thr.kill
172
+ end
173
+ end
174
+ end
175
+
176
+ #
177
+ # Reuse port if supported
178
+ #
179
+ def enable_reuse(server)
180
+ server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
181
+ begin
182
+ server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
183
+ rescue StandardError
184
+ # Not supported on all systems
185
+ nil
186
+ end
187
+ end
188
+
189
+ #
190
+ # Utility: Convert a status code to reason phrase
191
+ #
192
+ def http_status_message(status)
193
+ {
194
+ 200 => 'OK',
195
+ 404 => 'Not Found'
196
+ }[status] || 'Unknown'
197
+ end
198
+ end
199
+ 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
- def shifted_id = @shifted_id ||= object_id << 28
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
- if (ptr = POINTER_POOL.pop)
14
- ptr.parent = self
15
- ptr.parent = self
16
- ptr.frame = frame
17
- ptr.key = key
18
- ptr.depth = depth + 1
19
- ptr.pointer_cache&.clear
20
- ptr
21
- else
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}"}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LazyGraph
4
- VERSION = '0.1.3'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/lazy_graph.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
-
5
3
  # LazyGraph is a library which allows you to define a strictly typed hierarchical Graph structure.
6
4
  # Within this graph you can annotate certain nodes as derived nodes, which compute their outputs based
7
5
  # on input dependencies on other nodes in the graph structure (can be relative and absolute references).
@@ -12,11 +10,7 @@ require 'logger'
12
10
  #
13
11
  module LazyGraph
14
12
  class AbortError < StandardError; end
15
- class << self
16
- attr_accessor :logger
17
- end
18
-
19
- self.logger = Logger.new($stdout)
13
+ class ValidationError < StandardError; end
20
14
  end
21
15
 
22
16
  require_relative 'lazy_graph/context'
@@ -29,4 +23,8 @@ require_relative 'lazy_graph/hash_utils'
29
23
  require_relative 'lazy_graph/builder'
30
24
  require_relative 'lazy_graph/builder_group'
31
25
  require_relative 'lazy_graph/stack_pointer'
32
- require_relative 'lazy_graph/server'
26
+ require_relative 'lazy_graph/cli'
27
+ require_relative 'lazy_graph/rack_server'
28
+ require_relative 'lazy_graph/rack_app'
29
+ require_relative 'lazy_graph/logger'
30
+ require_relative 'lazy_graph/environment'
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.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2024-12-25 00:00:00.000000000 Z
10
+ date: 2025-01-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: json-schema
@@ -52,13 +52,13 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: rack
55
+ name: benchmark-ips
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
- type: :runtime
61
+ type: :development
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
@@ -66,7 +66,21 @@ dependencies:
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  - !ruby/object:Gem::Dependency
69
- name: ostruct
69
+ name: debug
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: fiddle
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - ">="
@@ -80,7 +94,7 @@ dependencies:
80
94
  - !ruby/object:Gem::Version
81
95
  version: '0'
82
96
  - !ruby/object:Gem::Dependency
83
- name: rubocop
97
+ name: listen
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - ">="
@@ -94,7 +108,7 @@ dependencies:
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0'
96
110
  - !ruby/object:Gem::Dependency
97
- name: fiddle
111
+ name: memory_profiler
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
100
114
  - - ">="
@@ -108,19 +122,61 @@ dependencies:
108
122
  - !ruby/object:Gem::Version
109
123
  version: '0'
110
124
  - !ruby/object:Gem::Dependency
111
- name: debug
125
+ name: observer
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: ostruct
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: rack
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rake
112
168
  requirement: !ruby/object:Gem::Requirement
113
169
  requirements:
114
170
  - - "~>"
115
171
  - !ruby/object:Gem::Version
116
- version: '1.0'
172
+ version: '13.0'
117
173
  type: :development
118
174
  prerelease: false
119
175
  version_requirements: !ruby/object:Gem::Requirement
120
176
  requirements:
121
177
  - - "~>"
122
178
  - !ruby/object:Gem::Version
123
- version: '1.0'
179
+ version: '13.0'
124
180
  - !ruby/object:Gem::Dependency
125
181
  name: rdoc
126
182
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +191,48 @@ dependencies:
135
191
  - - ">="
136
192
  - !ruby/object:Gem::Version
137
193
  version: '0'
194
+ - !ruby/object:Gem::Dependency
195
+ name: rubocop
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: solargraph
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ - !ruby/object:Gem::Dependency
223
+ name: vernier
224
+ requirement: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ type: :development
230
+ prerelease: false
231
+ version_requirements: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
138
236
  description: JSON Driven, Stateless Rules Engine for JIT and efficient evaluation
139
237
  of complex rules and computation graphs.
140
238
  email:
@@ -148,15 +246,20 @@ files:
148
246
  - LICENSE.txt
149
247
  - README.md
150
248
  - Rakefile
249
+ - examples/converter.rb
151
250
  - examples/performance_tests.rb
251
+ - examples/shopping_cart_totals.rb
152
252
  - lib/lazy_graph.rb
153
253
  - lib/lazy_graph/builder.rb
154
254
  - lib/lazy_graph/builder/dsl.rb
155
255
  - lib/lazy_graph/builder_group.rb
256
+ - lib/lazy_graph/cli.rb
156
257
  - lib/lazy_graph/context.rb
258
+ - lib/lazy_graph/environment.rb
157
259
  - lib/lazy_graph/graph.rb
158
260
  - lib/lazy_graph/hash_utils.rb
159
261
  - lib/lazy_graph/lazy-graph.json
262
+ - lib/lazy_graph/logger.rb
160
263
  - lib/lazy_graph/missing_value.rb
161
264
  - lib/lazy_graph/node.rb
162
265
  - lib/lazy_graph/node/array_node.rb
@@ -168,7 +271,8 @@ files:
168
271
  - lib/lazy_graph/path_parser/path.rb
169
272
  - lib/lazy_graph/path_parser/path_group.rb
170
273
  - lib/lazy_graph/path_parser/path_part.rb
171
- - lib/lazy_graph/server.rb
274
+ - lib/lazy_graph/rack_app.rb
275
+ - lib/lazy_graph/rack_server.rb
172
276
  - lib/lazy_graph/stack_pointer.rb
173
277
  - lib/lazy_graph/version.rb
174
278
  - logo.png