stellwerk-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +164 -0
- data/lib/stellwerk/errors.rb +18 -0
- data/lib/stellwerk/evaluator.rb +748 -0
- data/lib/stellwerk/flow.rb +88 -0
- data/lib/stellwerk/functions.rb +318 -0
- data/lib/stellwerk/result.rb +35 -0
- data/lib/stellwerk/version.rb +5 -0
- data/lib/stellwerk.rb +60 -0
- metadata +115 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Stellwerk
|
|
6
|
+
# Convenience wrapper for loading and executing compiled flows
|
|
7
|
+
class Flow
|
|
8
|
+
attr_reader :compiled_json, :path
|
|
9
|
+
|
|
10
|
+
# Load a flow from a JSON file
|
|
11
|
+
#
|
|
12
|
+
# @param path [String] Path to the compiled JSON file
|
|
13
|
+
# @return [Stellwerk::Flow]
|
|
14
|
+
# @raise [InvalidFlowError] if the file doesn't exist or contains invalid JSON
|
|
15
|
+
def self.load(path)
|
|
16
|
+
unless File.exist?(path)
|
|
17
|
+
raise InvalidFlowError, "Flow file not found: #{path}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
content = File.read(path)
|
|
21
|
+
json = JSON.parse(content)
|
|
22
|
+
|
|
23
|
+
new(json, path: path)
|
|
24
|
+
rescue JSON::ParserError => e
|
|
25
|
+
raise InvalidFlowError, "Invalid JSON in flow file: #{e.message}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Create a flow from a parsed JSON hash
|
|
29
|
+
#
|
|
30
|
+
# @param compiled_json [Hash] The compiled flow JSON
|
|
31
|
+
# @param path [String, nil] Optional path for debugging
|
|
32
|
+
def initialize(compiled_json, path: nil)
|
|
33
|
+
@compiled_json = compiled_json
|
|
34
|
+
@path = path
|
|
35
|
+
validate!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Execute the flow with the given parameters
|
|
39
|
+
#
|
|
40
|
+
# @param params [Hash] Input parameters for the flow
|
|
41
|
+
# @param sub_flows [Hash] Additional sub-flows for map nodes
|
|
42
|
+
# @param metadata [Hash] Additional metadata to merge into context
|
|
43
|
+
# @return [Stellwerk::Result]
|
|
44
|
+
def execute(params = {}, sub_flows: {}, metadata: {})
|
|
45
|
+
Evaluator.call(
|
|
46
|
+
compiled_json: @compiled_json,
|
|
47
|
+
params: params,
|
|
48
|
+
sub_flows: sub_flows,
|
|
49
|
+
metadata: metadata
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get the flow version from compiled JSON
|
|
54
|
+
def version
|
|
55
|
+
@compiled_json["version"] || @compiled_json[:version]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get compilation timestamp
|
|
59
|
+
def compiled_at
|
|
60
|
+
@compiled_json["compiled_at"] || @compiled_json[:compiled_at]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get entry node IDs
|
|
64
|
+
def entry_node_ids
|
|
65
|
+
@compiled_json["entry_node_ids"] || @compiled_json[:entry_node_ids] || []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get all node IDs
|
|
69
|
+
def node_ids
|
|
70
|
+
nodes = @compiled_json["nodes"] || @compiled_json[:nodes] || {}
|
|
71
|
+
nodes.keys
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def validate!
|
|
77
|
+
nodes = @compiled_json["nodes"] || @compiled_json[:nodes]
|
|
78
|
+
unless nodes.is_a?(Hash)
|
|
79
|
+
raise InvalidFlowError, "Compiled flow must have a 'nodes' hash"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
entry_ids = @compiled_json["entry_node_ids"] || @compiled_json[:entry_node_ids]
|
|
83
|
+
if nodes.any? && (entry_ids.nil? || entry_ids.empty?)
|
|
84
|
+
Stellwerk.logger.warn("Flow has nodes but no entry_node_ids defined")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dentaku"
|
|
4
|
+
|
|
5
|
+
# Collection & aggregation helper functions for Dentaku.
|
|
6
|
+
# We override common aggregators (SUM, COUNT, MIN, MAX) to be dual-mode:
|
|
7
|
+
# - Classic Dentaku usage: SUM(a, b, c)
|
|
8
|
+
# - Array mode: SUM(array) or SUM(array_of_numbers) or SUM(array_of_mixed)
|
|
9
|
+
# Additional functions: FIRST, LAST, TAKE, PROJECT (internal helper)
|
|
10
|
+
# Projection pattern: identifier[*].field is preprocessed into PROJECT(identifier, "field")
|
|
11
|
+
# Mixed-type coercion:
|
|
12
|
+
# - Numeric values kept
|
|
13
|
+
# - Numeric-looking strings converted to Float
|
|
14
|
+
# - nil ignored
|
|
15
|
+
# - Other types ignored for numeric aggregations
|
|
16
|
+
# - If no numeric values remain -> SUM = 0, MIN/MAX = nil, COUNT counts *all non-nil projected elements* (not just numeric)
|
|
17
|
+
# TAKE(array, n) -> first n elements (n coerced to integer, negative treated as 0)
|
|
18
|
+
# FIRST(array) / LAST(array) -> element or nil
|
|
19
|
+
|
|
20
|
+
module Stellwerk
|
|
21
|
+
module Functions
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def ensure_array(arg)
|
|
25
|
+
return [] if arg.nil?
|
|
26
|
+
return arg if arg.is_a?(Array)
|
|
27
|
+
[arg]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def coerce_numeric_list(list)
|
|
31
|
+
list.filter_map do |v|
|
|
32
|
+
case v
|
|
33
|
+
when Numeric then v.to_f
|
|
34
|
+
when String
|
|
35
|
+
v_strip = v.strip
|
|
36
|
+
if v_strip.match?(/^[-+]?\d+(?:\.\d+)?$/)
|
|
37
|
+
v_strip.to_f
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Internal projection helper: given an array of hashes (or objects responding to [] / .public_send)
|
|
46
|
+
# and a field name, extract that field, allowing nils.
|
|
47
|
+
# Named PROJECT (instead of __project) because Dentaku will constantize function names.
|
|
48
|
+
def PROJECT(collection, field_name)
|
|
49
|
+
arr = ensure_array(collection)
|
|
50
|
+
arr.map do |item|
|
|
51
|
+
if item.is_a?(Hash)
|
|
52
|
+
item[field_name.to_sym] || item[field_name.to_s]
|
|
53
|
+
elsif item.respond_to?(:[]) && !item.is_a?(String)
|
|
54
|
+
begin
|
|
55
|
+
item[field_name]
|
|
56
|
+
rescue StandardError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
elsif item.respond_to?(field_name)
|
|
60
|
+
begin
|
|
61
|
+
item.public_send(field_name)
|
|
62
|
+
rescue StandardError
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# DEEP_PROJECT(root, path_string, field_name)
|
|
72
|
+
# Traverse a dotted path to reach an array, then extract field_name from each element.
|
|
73
|
+
# Example: DEEP_PROJECT(order, "items", "price") where order[:items] is an array of hashes.
|
|
74
|
+
def DEEP_PROJECT(root, path_string, field_name)
|
|
75
|
+
return [] if root.nil?
|
|
76
|
+
current = root
|
|
77
|
+
path_segments = path_string.to_s.split(".")
|
|
78
|
+
path_segments.each do |seg|
|
|
79
|
+
break if current.nil?
|
|
80
|
+
if current.is_a?(Hash)
|
|
81
|
+
current = current[seg.to_sym] || current[seg.to_s]
|
|
82
|
+
elsif current.respond_to?(seg)
|
|
83
|
+
begin
|
|
84
|
+
current = current.public_send(seg)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
current = nil
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
current = nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
arr = ensure_array(current)
|
|
93
|
+
arr.map do |item|
|
|
94
|
+
if item.is_a?(Hash)
|
|
95
|
+
item[field_name.to_sym] || item[field_name.to_s]
|
|
96
|
+
elsif item.respond_to?(:[]) && !item.is_a?(String)
|
|
97
|
+
begin
|
|
98
|
+
item[field_name]
|
|
99
|
+
rescue StandardError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
elsif item.respond_to?(field_name)
|
|
103
|
+
begin
|
|
104
|
+
item.public_send(field_name)
|
|
105
|
+
rescue StandardError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# SUM dual mode
|
|
115
|
+
def SUM(*args)
|
|
116
|
+
values = if args.length == 1 && args.first.is_a?(Array)
|
|
117
|
+
coerce_numeric_list(args.first)
|
|
118
|
+
else
|
|
119
|
+
coerce_numeric_list(args)
|
|
120
|
+
end
|
|
121
|
+
values.sum
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# COUNT dual mode: counts non-nil entries. If single array arg, count its non-nil members.
|
|
125
|
+
def COUNT(*args)
|
|
126
|
+
if args.length == 1 && args.first.is_a?(Array)
|
|
127
|
+
args.first.compact.length
|
|
128
|
+
else
|
|
129
|
+
args.compact.length
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# MIN dual mode: returns nil if no numeric values
|
|
134
|
+
def MIN(*args)
|
|
135
|
+
values = if args.length == 1 && args.first.is_a?(Array)
|
|
136
|
+
coerce_numeric_list(args.first)
|
|
137
|
+
else
|
|
138
|
+
coerce_numeric_list(args)
|
|
139
|
+
end
|
|
140
|
+
values.min
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# MAX dual mode: returns nil if no numeric values
|
|
144
|
+
def MAX(*args)
|
|
145
|
+
values = if args.length == 1 && args.first.is_a?(Array)
|
|
146
|
+
coerce_numeric_list(args.first)
|
|
147
|
+
else
|
|
148
|
+
coerce_numeric_list(args)
|
|
149
|
+
end
|
|
150
|
+
values.max
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def FIRST(arg)
|
|
154
|
+
arr = ensure_array(arg)
|
|
155
|
+
arr.first
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def LAST(arg)
|
|
159
|
+
arr = ensure_array(arg)
|
|
160
|
+
arr.last
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def TAKE(arg, n)
|
|
164
|
+
arr = ensure_array(arg)
|
|
165
|
+
limit = begin
|
|
166
|
+
Integer(n)
|
|
167
|
+
rescue StandardError
|
|
168
|
+
0
|
|
169
|
+
end
|
|
170
|
+
return [] if limit <= 0
|
|
171
|
+
arr.first(limit)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# -----------------------------
|
|
175
|
+
# Phase 2 Functions
|
|
176
|
+
# -----------------------------
|
|
177
|
+
# MAP(collection, expr_string, safe: false)
|
|
178
|
+
# - Applies a Dentaku expression to each element with variable 'item' bound to element
|
|
179
|
+
# FILTER(collection, predicate_expr)
|
|
180
|
+
# - Keeps elements where predicate_expr evaluates truthy
|
|
181
|
+
# DISTINCT(collection)
|
|
182
|
+
# - Removes duplicates via Ruby equality
|
|
183
|
+
# AVERAGE/AVG dual-mode like SUM but returns nil if no numeric values
|
|
184
|
+
# SUMIF(collection, predicate_expr, projection_expr=nil)
|
|
185
|
+
# - If projection_expr provided, sum of projection for items where predicate matches
|
|
186
|
+
# - Else sum of numeric-looking items themselves
|
|
187
|
+
# COUNTIF(collection, predicate_expr)
|
|
188
|
+
# - Count of elements where predicate is truthy
|
|
189
|
+
# TRACE(value, label=nil)
|
|
190
|
+
# - Returns value unchanged; placeholder for future instrumentation hook
|
|
191
|
+
|
|
192
|
+
def MAP(collection, expr, calculator: Dentaku::Calculator.new)
|
|
193
|
+
arr = ensure_array(collection)
|
|
194
|
+
return [] if expr.nil? || expr.strip.empty?
|
|
195
|
+
arr.map do |item|
|
|
196
|
+
begin
|
|
197
|
+
# Provide 'item' binding; if item is a Hash, flatten simple keys
|
|
198
|
+
ctx = { "item" => item }
|
|
199
|
+
if item.is_a?(Hash)
|
|
200
|
+
item.each { |k, v| ctx[k.to_s] = v }
|
|
201
|
+
end
|
|
202
|
+
calculator.evaluate!(expr, ctx)
|
|
203
|
+
rescue StandardError
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def FILTER(collection, predicate_expr, calculator: Dentaku::Calculator.new)
|
|
210
|
+
arr = ensure_array(collection)
|
|
211
|
+
return arr if predicate_expr.nil? || predicate_expr.strip.empty?
|
|
212
|
+
arr.select do |item|
|
|
213
|
+
begin
|
|
214
|
+
ctx = { "item" => item }
|
|
215
|
+
if item.is_a?(Hash)
|
|
216
|
+
item.each { |k, v| ctx[k.to_s] = v }
|
|
217
|
+
end
|
|
218
|
+
!!calculator.evaluate!(predicate_expr, ctx)
|
|
219
|
+
rescue StandardError
|
|
220
|
+
false
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def DISTINCT(collection)
|
|
226
|
+
ensure_array(collection).uniq
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def AVERAGE(*args)
|
|
230
|
+
values = if args.length == 1 && args.first.is_a?(Array)
|
|
231
|
+
coerce_numeric_list(args.first)
|
|
232
|
+
else
|
|
233
|
+
coerce_numeric_list(args)
|
|
234
|
+
end
|
|
235
|
+
return nil if values.empty?
|
|
236
|
+
values.sum / values.length.to_f
|
|
237
|
+
end
|
|
238
|
+
alias AVG AVERAGE
|
|
239
|
+
|
|
240
|
+
def SUMIF(collection, predicate_expr, projection_expr = nil, calculator: Dentaku::Calculator.new)
|
|
241
|
+
arr = ensure_array(collection)
|
|
242
|
+
return 0 if predicate_expr.nil? || predicate_expr.strip.empty?
|
|
243
|
+
total = 0.0
|
|
244
|
+
arr.each do |item|
|
|
245
|
+
begin
|
|
246
|
+
ctx = { "item" => item }
|
|
247
|
+
if item.is_a?(Hash)
|
|
248
|
+
item.each { |k, v| ctx[k.to_s] = v }
|
|
249
|
+
end
|
|
250
|
+
next unless calculator.evaluate!(predicate_expr, ctx)
|
|
251
|
+
value = if projection_expr && !projection_expr.strip.empty?
|
|
252
|
+
calculator.evaluate!(projection_expr, ctx) rescue nil
|
|
253
|
+
else
|
|
254
|
+
item
|
|
255
|
+
end
|
|
256
|
+
case value
|
|
257
|
+
when Numeric
|
|
258
|
+
total += value.to_f
|
|
259
|
+
when String
|
|
260
|
+
v = value.strip
|
|
261
|
+
total += v.to_f if v.match?(/^[-+]?\d+(?:\.\d+)?$/)
|
|
262
|
+
end
|
|
263
|
+
rescue StandardError
|
|
264
|
+
next
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
total
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def COUNTIF(collection, predicate_expr, calculator: Dentaku::Calculator.new)
|
|
271
|
+
arr = ensure_array(collection)
|
|
272
|
+
return 0 if predicate_expr.nil? || predicate_expr.strip.empty?
|
|
273
|
+
count = 0
|
|
274
|
+
arr.each do |item|
|
|
275
|
+
begin
|
|
276
|
+
ctx = { "item" => item }
|
|
277
|
+
if item.is_a?(Hash)
|
|
278
|
+
item.each { |k, v| ctx[k.to_s] = v }
|
|
279
|
+
end
|
|
280
|
+
count += 1 if calculator.evaluate!(predicate_expr, ctx)
|
|
281
|
+
rescue StandardError
|
|
282
|
+
# ignore
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
count
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def REDUCE(collection, initial, expr, calculator: Dentaku::Calculator.new)
|
|
289
|
+
arr = ensure_array(collection)
|
|
290
|
+
return initial if arr.empty?
|
|
291
|
+
|
|
292
|
+
accumulator = initial
|
|
293
|
+
arr.each do |item|
|
|
294
|
+
begin
|
|
295
|
+
ctx = { "item" => item, "accumulator" => accumulator, "acc" => accumulator }
|
|
296
|
+
if item.is_a?(Hash)
|
|
297
|
+
item.each { |k, v| ctx[k.to_s] = v }
|
|
298
|
+
end
|
|
299
|
+
accumulator = calculator.evaluate!(expr, ctx)
|
|
300
|
+
rescue StandardError
|
|
301
|
+
nil
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
accumulator
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def TRACE(value, _label = nil)
|
|
308
|
+
# Future: push to instrumentation buffer
|
|
309
|
+
value
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# ARRAY function - creates an array from arguments
|
|
313
|
+
# Allows: values = ARRAY(1, 2, 3) which can then be used with SUM(values)
|
|
314
|
+
def ARRAY(*args)
|
|
315
|
+
args
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stellwerk
|
|
4
|
+
# Represents the result of a flow execution
|
|
5
|
+
class Result
|
|
6
|
+
attr_reader :outputs, :errors, :applied_nodes, :context
|
|
7
|
+
|
|
8
|
+
def initialize(outputs:, errors:, applied_nodes:, context:)
|
|
9
|
+
@outputs = outputs || {}
|
|
10
|
+
@errors = errors || []
|
|
11
|
+
@applied_nodes = applied_nodes || []
|
|
12
|
+
@context = context || {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns true if the flow executed without errors
|
|
16
|
+
def success?
|
|
17
|
+
errors.empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true if there were any errors during execution
|
|
21
|
+
def failure?
|
|
22
|
+
!success?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Convert to hash representation
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
outputs: outputs,
|
|
29
|
+
errors: errors,
|
|
30
|
+
applied_nodes: applied_nodes,
|
|
31
|
+
context: context
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/stellwerk.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
require_relative "stellwerk/version"
|
|
6
|
+
require_relative "stellwerk/errors"
|
|
7
|
+
require_relative "stellwerk/result"
|
|
8
|
+
require_relative "stellwerk/functions"
|
|
9
|
+
require_relative "stellwerk/evaluator"
|
|
10
|
+
require_relative "stellwerk/flow"
|
|
11
|
+
|
|
12
|
+
# Stellwerk - A standalone Ruby gem for evaluating pre-compiled flow JSON
|
|
13
|
+
#
|
|
14
|
+
# This gem provides a Rails-free implementation for executing flows that have
|
|
15
|
+
# been compiled by the Stellwerk platform. It works with the compiled JSON
|
|
16
|
+
# format that includes all node definitions, adjacency lists, and embedded
|
|
17
|
+
# sub-flows.
|
|
18
|
+
#
|
|
19
|
+
# @example Simple usage
|
|
20
|
+
# flow = Stellwerk::Flow.load('pricing.compiled.json')
|
|
21
|
+
# result = flow.execute(quantity: 10, price: 25.00)
|
|
22
|
+
#
|
|
23
|
+
# if result.success?
|
|
24
|
+
# puts result.outputs[:total]
|
|
25
|
+
# else
|
|
26
|
+
# puts result.errors
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Direct evaluator usage
|
|
30
|
+
# json = JSON.parse(File.read('flow.json'))
|
|
31
|
+
# result = Stellwerk::Evaluator.call(compiled_json: json, params: { x: 10 })
|
|
32
|
+
#
|
|
33
|
+
# @example Configuration
|
|
34
|
+
# Stellwerk.configure do |config|
|
|
35
|
+
# config.logger = Logger.new(STDOUT)
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
module Stellwerk
|
|
39
|
+
class << self
|
|
40
|
+
# Configuration options
|
|
41
|
+
attr_writer :logger
|
|
42
|
+
|
|
43
|
+
# Returns the configured logger (defaults to a null logger)
|
|
44
|
+
def logger
|
|
45
|
+
@logger ||= Logger.new(File::NULL)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Configure Stellwerk
|
|
49
|
+
#
|
|
50
|
+
# @yield [self] Yields self for configuration
|
|
51
|
+
def configure
|
|
52
|
+
yield self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reset configuration to defaults
|
|
56
|
+
def reset!
|
|
57
|
+
@logger = nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stellwerk-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Roadwerk
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: dentaku
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.12'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.12'
|
|
69
|
+
description: |
|
|
70
|
+
Stellwerk is a standalone Ruby gem for evaluating pre-compiled flow JSON
|
|
71
|
+
without any Rails or ActiveRecord dependencies. It provides a fast, portable
|
|
72
|
+
way to execute decision logic and calculations defined in the Stellwerk platform.
|
|
73
|
+
email:
|
|
74
|
+
- team@roadwerk.io
|
|
75
|
+
executables: []
|
|
76
|
+
extensions: []
|
|
77
|
+
extra_rdoc_files: []
|
|
78
|
+
files:
|
|
79
|
+
- CHANGELOG.md
|
|
80
|
+
- LICENSE.txt
|
|
81
|
+
- README.md
|
|
82
|
+
- lib/stellwerk.rb
|
|
83
|
+
- lib/stellwerk/errors.rb
|
|
84
|
+
- lib/stellwerk/evaluator.rb
|
|
85
|
+
- lib/stellwerk/flow.rb
|
|
86
|
+
- lib/stellwerk/functions.rb
|
|
87
|
+
- lib/stellwerk/result.rb
|
|
88
|
+
- lib/stellwerk/version.rb
|
|
89
|
+
homepage: https://github.com/roadwerk/stellwerk-ruby
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata:
|
|
93
|
+
homepage_uri: https://github.com/roadwerk/stellwerk-ruby
|
|
94
|
+
source_code_uri: https://github.com/roadwerk/stellwerk-ruby
|
|
95
|
+
changelog_uri: https://github.com/roadwerk/stellwerk-ruby/blob/main/CHANGELOG.md
|
|
96
|
+
post_install_message:
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: 3.0.0
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 3.4.10
|
|
112
|
+
signing_key:
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: Evaluate pre-compiled Stellwerk flows in pure Ruby
|
|
115
|
+
test_files: []
|