kbs 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +291 -362
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +865 -0
- data/docs/advanced/testing.md +827 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +1047 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1104 -0
- data/docs/architecture/blackboard.md +544 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/fact-rule-relationship.svg +65 -0
- data/docs/assets/images/fact-structure.svg +42 -0
- data/docs/assets/images/inference-cycle.svg +47 -0
- data/docs/assets/images/kb-components.svg +43 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/rule-structure.svg +44 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/index.md +223 -0
- data/docs/guides/blackboard-memory.md +589 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +385 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +914 -0
- data/docs/index.md +155 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +221 -0
- data/docs/what-is-a-fact.md +694 -0
- data/docs/what-is-a-knowledge-base.md +350 -0
- data/docs/what-is-a-rule.md +833 -0
- data/examples/.gitignore +1 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -6
- data/examples/concurrent_inference_demo_dsl.rb +362 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/iot_demo_using_dsl.rb +1 -1
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/rule_source_demo.rb +123 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp_dsl.txt +9392 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/decompiler.rb +204 -0
- data/lib/kbs/dsl/knowledge_base.rb +101 -2
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/dsl.rb +3 -1
- data/lib/kbs/{rete_engine.rb → engine.rb} +42 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +15 -13
- data/mkdocs.yml +181 -0
- metadata +74 -9
- data/examples/stock_trading_system.rb.bak +0 -563
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs/blackboard'
|
|
4
|
+
|
|
5
|
+
puts "Redis-Backed High-Frequency Trading System"
|
|
6
|
+
puts "=" * 70
|
|
7
|
+
|
|
8
|
+
# Demo 1: Pure Redis Store (fast, in-memory)
|
|
9
|
+
puts "\n=== Pure Redis Store Demo ==="
|
|
10
|
+
puts "Fast in-memory fact storage with Redis"
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
redis_store = KBS::Blackboard::Persistence::RedisStore.new(
|
|
14
|
+
url: 'redis://localhost:6379/1' # Use DB 1 for demo
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Clear previous demo data
|
|
18
|
+
redis_store.redis.flushdb
|
|
19
|
+
puts "(Cleared previous Redis data)"
|
|
20
|
+
|
|
21
|
+
engine = KBS::Blackboard::Engine.new(store: redis_store)
|
|
22
|
+
|
|
23
|
+
# Add trading rules (raw API since Blackboard::Engine doesn't use DSL)
|
|
24
|
+
spread_rule = KBS::Rule.new('tight_spread_opportunity') do |r|
|
|
25
|
+
r.conditions << KBS::Condition.new(:market_price, { symbol: 'AAPL' })
|
|
26
|
+
r.conditions << KBS::Condition.new(:order, { symbol: 'AAPL', type: 'BUY' })
|
|
27
|
+
r.action = ->(facts) {
|
|
28
|
+
price = facts.find { |f| f.type == :market_price }
|
|
29
|
+
order = facts.find { |f| f.type == :order }
|
|
30
|
+
spread = 0.02 # Tight spread
|
|
31
|
+
puts "\n💹 TRADING SIGNAL: Tight spread opportunity for #{price[:symbol]}"
|
|
32
|
+
puts " Price: $#{price[:price]}, Spread: $#{spread}"
|
|
33
|
+
puts " Order: #{order[:type]} #{order[:quantity]} @ $#{order[:limit]}"
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
high_volume_rule = KBS::Rule.new('high_volume_alert') do |r|
|
|
38
|
+
r.conditions << KBS::Condition.new(:market_price, { volume: ->(v) { v > 800 } })
|
|
39
|
+
r.action = ->(facts) {
|
|
40
|
+
price = facts.first
|
|
41
|
+
puts "\n📊 HIGH VOLUME: #{price[:symbol]} trading at #{price[:volume]} shares"
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
engine.add_rule(spread_rule)
|
|
46
|
+
engine.add_rule(high_volume_rule)
|
|
47
|
+
|
|
48
|
+
puts "\nAdding high-frequency market data..."
|
|
49
|
+
price1 = engine.add_fact(:market_price, { symbol: "AAPL", price: 150.25, volume: 1000 })
|
|
50
|
+
price2 = engine.add_fact(:market_price, { symbol: "GOOGL", price: 2800.50, volume: 500 })
|
|
51
|
+
order = engine.add_fact(:order, { symbol: "AAPL", type: "BUY", quantity: 100, limit: 150.00 })
|
|
52
|
+
|
|
53
|
+
puts "Facts added with UUIDs:"
|
|
54
|
+
puts " AAPL Price: #{price1.uuid}"
|
|
55
|
+
puts " GOOGL Price: #{price2.uuid}"
|
|
56
|
+
puts " Order: #{order.uuid}"
|
|
57
|
+
|
|
58
|
+
puts "\nRunning inference engine..."
|
|
59
|
+
engine.run
|
|
60
|
+
|
|
61
|
+
puts "\nPosting trading messages..."
|
|
62
|
+
engine.post_message("MarketDataFeed", "prices", { symbol: "AAPL", bid: 150.24, ask: 150.26 }, priority: 10)
|
|
63
|
+
engine.post_message("OrderManager", "orders", { action: "fill", order_id: order.uuid }, priority: 5)
|
|
64
|
+
|
|
65
|
+
puts "\nConsuming highest priority message..."
|
|
66
|
+
message = engine.consume_message("prices", "TradingStrategy")
|
|
67
|
+
puts " Received: #{message[:content]}" if message
|
|
68
|
+
|
|
69
|
+
puts "\nQuerying market prices..."
|
|
70
|
+
prices = engine.blackboard.get_facts(:market_price)
|
|
71
|
+
puts " Found #{prices.size} price(s):"
|
|
72
|
+
prices.each { |p| puts " - #{p}" }
|
|
73
|
+
|
|
74
|
+
puts "\nRedis Statistics:"
|
|
75
|
+
stats = engine.stats
|
|
76
|
+
stats.each do |key, value|
|
|
77
|
+
puts " #{key.to_s.gsub('_', ' ').capitalize}: #{value}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
engine.blackboard.close
|
|
81
|
+
|
|
82
|
+
rescue Redis::CannotConnectError => e
|
|
83
|
+
puts "\n⚠️ Redis not available: #{e.message}"
|
|
84
|
+
puts " Please start Redis: redis-server"
|
|
85
|
+
puts " Or install Redis: brew install redis (macOS)"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Demo 2: Hybrid Store (Redis + SQLite)
|
|
89
|
+
puts "\n\n=== Hybrid Store Demo ==="
|
|
90
|
+
puts "Redis for fast fact access + SQLite for durable audit trail"
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
hybrid_store = KBS::Blackboard::Persistence::HybridStore.new(
|
|
94
|
+
redis_url: 'redis://localhost:6379/2', # Use DB 2 for demo
|
|
95
|
+
db_path: ':memory:' # In-memory SQLite for demo
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Clear previous demo data from Redis
|
|
99
|
+
hybrid_store.redis_store.redis.flushdb
|
|
100
|
+
puts "(Cleared previous Redis data)"
|
|
101
|
+
|
|
102
|
+
engine = KBS::Blackboard::Engine.new(store: hybrid_store)
|
|
103
|
+
|
|
104
|
+
# Add monitoring rules (raw API since Blackboard::Engine doesn't use DSL)
|
|
105
|
+
temp_alert = KBS::Rule.new('temperature_alert') do |r|
|
|
106
|
+
r.conditions << KBS::Condition.new(:sensor, {
|
|
107
|
+
type: 'temperature',
|
|
108
|
+
value: ->(v) { v > 25 }
|
|
109
|
+
})
|
|
110
|
+
r.action = ->(facts) {
|
|
111
|
+
sensor = facts.first
|
|
112
|
+
puts "\n🌡️ TEMPERATURE ALERT: #{sensor[:location]} at #{sensor[:value]}°C (threshold: 25°C)"
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
cpu_warning = KBS::Rule.new('cpu_warning') do |r|
|
|
117
|
+
r.conditions << KBS::Condition.new(:sensor, {
|
|
118
|
+
type: 'cpu_usage',
|
|
119
|
+
value: ->(v) { v > 40 }
|
|
120
|
+
})
|
|
121
|
+
r.action = ->(facts) {
|
|
122
|
+
sensor = facts.first
|
|
123
|
+
puts "\n⚙️ CPU WARNING: #{sensor[:location]} at #{sensor[:value]}% (threshold: 40%)"
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
engine.add_rule(temp_alert)
|
|
128
|
+
engine.add_rule(cpu_warning)
|
|
129
|
+
|
|
130
|
+
puts "\nAdding facts (stored in Redis)..."
|
|
131
|
+
sensor1 = engine.add_fact(:sensor, { location: "trading_floor", type: "temperature", value: 22 })
|
|
132
|
+
sensor2 = engine.add_fact(:sensor, { location: "data_center", type: "cpu_usage", value: 45 })
|
|
133
|
+
|
|
134
|
+
puts "Facts added:"
|
|
135
|
+
puts " Sensor 1: #{sensor1.uuid}"
|
|
136
|
+
puts " Sensor 2: #{sensor2.uuid}"
|
|
137
|
+
|
|
138
|
+
puts "\nRunning inference engine..."
|
|
139
|
+
engine.run
|
|
140
|
+
|
|
141
|
+
puts "\nUpdating sensor value (audit logged to SQLite)..."
|
|
142
|
+
sensor1[:value] = 28
|
|
143
|
+
|
|
144
|
+
puts "\nRunning inference again after update..."
|
|
145
|
+
engine.run
|
|
146
|
+
|
|
147
|
+
puts "\nFact History from SQLite Audit Log:"
|
|
148
|
+
history = engine.blackboard.get_history(limit: 5)
|
|
149
|
+
history.each do |entry|
|
|
150
|
+
puts " [#{entry[:timestamp].strftime('%H:%M:%S')}] #{entry[:action]}: #{entry[:fact_type]}(#{entry[:attributes]})"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
puts "\nHybrid Store Benefits:"
|
|
154
|
+
puts " ✓ Facts in Redis (fast reads/writes)"
|
|
155
|
+
puts " ✓ Messages in Redis (real-time messaging)"
|
|
156
|
+
puts " ✓ Audit trail in SQLite (durable, queryable)"
|
|
157
|
+
puts " ✓ Best of both worlds for production systems"
|
|
158
|
+
|
|
159
|
+
puts "\nStatistics:"
|
|
160
|
+
stats = engine.stats
|
|
161
|
+
stats.each do |key, value|
|
|
162
|
+
puts " #{key.to_s.gsub('_', ' ').capitalize}: #{value}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
engine.blackboard.close
|
|
166
|
+
|
|
167
|
+
rescue Redis::CannotConnectError => e
|
|
168
|
+
puts "\n⚠️ Redis not available: #{e.message}"
|
|
169
|
+
puts " Hybrid store requires Redis for fact storage"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
puts "\n" + "=" * 70
|
|
173
|
+
puts "Demo complete!"
|
|
174
|
+
puts "\nComparison:"
|
|
175
|
+
puts " SQLite Store: Durable, transactional, embedded (no server needed)"
|
|
176
|
+
puts " Redis Store: Fast (100x), distributed, requires Redis server"
|
|
177
|
+
puts " Hybrid Store: Fast facts + durable audit (production recommended)"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs'
|
|
4
|
+
|
|
5
|
+
puts "Rule Source Introspection Demo"
|
|
6
|
+
puts "=" * 60
|
|
7
|
+
puts
|
|
8
|
+
puts "KBS can show the source of any rule — whether it was defined"
|
|
9
|
+
puts "in a file (exact source) or built dynamically at runtime"
|
|
10
|
+
puts "(reconstructed from bytecode via KBS::Decompiler)."
|
|
11
|
+
puts
|
|
12
|
+
|
|
13
|
+
# ── Part 1: File-defined rules ───────────────────────────────
|
|
14
|
+
|
|
15
|
+
puts "PART 1: File-Defined Rules"
|
|
16
|
+
puts "-" * 60
|
|
17
|
+
puts
|
|
18
|
+
|
|
19
|
+
kb = KBS.knowledge_base do
|
|
20
|
+
rule "dead_battery" do
|
|
21
|
+
on :symptom, problem: "won't start"
|
|
22
|
+
on :symptom, problem: "no lights"
|
|
23
|
+
|
|
24
|
+
perform do |facts, bindings|
|
|
25
|
+
puts "DIAGNOSIS: Dead battery"
|
|
26
|
+
puts "RECOMMENDATION: Replace or jump-start the battery"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rule "overheating" do
|
|
31
|
+
on :symptom, problem: "high temperature"
|
|
32
|
+
on :symptom, problem: "steam from hood"
|
|
33
|
+
without :symptom, problem: "coolant leak"
|
|
34
|
+
|
|
35
|
+
perform do |facts, bindings|
|
|
36
|
+
puts "DIAGNOSIS: Engine overheating (no coolant leak)"
|
|
37
|
+
puts "RECOMMENDATION: Check radiator and cooling system"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
kb.rules.each_key do |name|
|
|
43
|
+
puts "Source for '#{name}':"
|
|
44
|
+
puts
|
|
45
|
+
kb.print_rule_source(name)
|
|
46
|
+
puts
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# ── Part 2: Dynamically-created rules ────────────────────────
|
|
50
|
+
|
|
51
|
+
puts
|
|
52
|
+
puts "PART 2: Dynamically-Created Rules"
|
|
53
|
+
puts "-" * 60
|
|
54
|
+
puts
|
|
55
|
+
puts "These rules have no source file — KBS reconstructs them"
|
|
56
|
+
puts "from their internal state using YARV bytecode decompilation."
|
|
57
|
+
puts
|
|
58
|
+
|
|
59
|
+
# Simulate what an AI agent framework might do: build rules at
|
|
60
|
+
# runtime from configuration data, user input, or LLM output.
|
|
61
|
+
|
|
62
|
+
dynamic_kb = KBS::DSL::KnowledgeBase.new
|
|
63
|
+
|
|
64
|
+
# Rule with lambda condition helpers
|
|
65
|
+
builder = KBS::DSL::RuleBuilder.new("high_temp_alert")
|
|
66
|
+
builder.desc "Fire when temperature exceeds safe threshold"
|
|
67
|
+
builder.priority 5
|
|
68
|
+
builder.on :sensor, location: "server_room", temp: ->(v) { v > 85 }
|
|
69
|
+
builder.without :alert, type: :cooling_active
|
|
70
|
+
builder.perform { |facts| puts "ALERT: Server room temperature critical!" }
|
|
71
|
+
|
|
72
|
+
rule = builder.build
|
|
73
|
+
dynamic_kb.instance_variable_get(:@rule_builders)["high_temp_alert"] = builder
|
|
74
|
+
dynamic_kb.instance_variable_get(:@rules)["high_temp_alert"] = rule
|
|
75
|
+
dynamic_kb.engine.add_rule(rule)
|
|
76
|
+
|
|
77
|
+
# Rule with multiple proc-based conditions
|
|
78
|
+
builder2 = KBS::DSL::RuleBuilder.new("buy_signal")
|
|
79
|
+
builder2.on :stock, price: ->(v) { v < 150 }, volume: ->(v) { v > 1_000_000 }
|
|
80
|
+
builder2.without :position, status: :open
|
|
81
|
+
builder2.perform { |facts| puts "BUY signal triggered" }
|
|
82
|
+
|
|
83
|
+
rule2 = builder2.build
|
|
84
|
+
dynamic_kb.instance_variable_get(:@rule_builders)["buy_signal"] = builder2
|
|
85
|
+
dynamic_kb.instance_variable_get(:@rules)["buy_signal"] = rule2
|
|
86
|
+
dynamic_kb.engine.add_rule(rule2)
|
|
87
|
+
|
|
88
|
+
["high_temp_alert", "buy_signal"].each do |name|
|
|
89
|
+
puts "Reconstructed source for '#{name}':"
|
|
90
|
+
puts
|
|
91
|
+
dynamic_kb.print_rule_source(name)
|
|
92
|
+
puts
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ── Part 3: Decompiler standalone ─────────────────────────────
|
|
96
|
+
|
|
97
|
+
puts
|
|
98
|
+
puts "PART 3: KBS::Decompiler Standalone"
|
|
99
|
+
puts "-" * 60
|
|
100
|
+
puts
|
|
101
|
+
puts "The decompiler works on any Proc or Lambda:"
|
|
102
|
+
puts
|
|
103
|
+
|
|
104
|
+
examples = {
|
|
105
|
+
"lambda" => ->(x) { x * 2 + 1 },
|
|
106
|
+
"proc" => proc { |a, b| a > b },
|
|
107
|
+
"complex" => ->(items) { items.select { |x| x > 0 }.map { |x| x * 2 } },
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
examples.each do |label, pr|
|
|
111
|
+
puts " #{label}:"
|
|
112
|
+
puts " decompile => #{KBS::Decompiler.new(pr).decompile}"
|
|
113
|
+
puts " decompile_block => #{KBS::Decompiler.new(pr).decompile_block}"
|
|
114
|
+
puts
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ── Part 4: Unknown rule ──────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
puts "PART 4: Unknown Rule"
|
|
120
|
+
puts "-" * 60
|
|
121
|
+
puts
|
|
122
|
+
kb.print_rule_source("nonexistent")
|
|
123
|
+
puts " rule_source returns: #{kb.rule_source('nonexistent').inspect}"
|
data/examples/run_all.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run all example files in the examples directory
|
|
5
|
+
|
|
6
|
+
require 'pathname'
|
|
7
|
+
|
|
8
|
+
# Get the directory where this script is located
|
|
9
|
+
examples_dir = Pathname.new(__FILE__).dirname
|
|
10
|
+
|
|
11
|
+
# Find all Ruby files except this one
|
|
12
|
+
example_files = Dir.glob(examples_dir.join('*.rb'))
|
|
13
|
+
.reject { |f| File.basename(f).start_with? 'run_all' }
|
|
14
|
+
.reject { |f| File.basename(f).end_with? '_dsl.rb' }
|
|
15
|
+
.sort
|
|
16
|
+
|
|
17
|
+
puts
|
|
18
|
+
puts "Running #{example_files.size} examples from #{examples_dir}"
|
|
19
|
+
puts
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
example_files.each_with_index do |file, index|
|
|
23
|
+
filename = File.basename(file)
|
|
24
|
+
filename_size = filename.size + 6
|
|
25
|
+
|
|
26
|
+
STDERR.puts "Running example #{index + 1}/#{example_files.size}: #{filename} ..."
|
|
27
|
+
|
|
28
|
+
puts
|
|
29
|
+
puts "=" * filename_size
|
|
30
|
+
puts "## #{filename} ##"
|
|
31
|
+
puts "=" * filename_size
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
# Run the example
|
|
35
|
+
system("ruby", file)
|
|
36
|
+
|
|
37
|
+
exit_status = $?.exitstatus
|
|
38
|
+
|
|
39
|
+
if exit_status != 0
|
|
40
|
+
puts
|
|
41
|
+
puts "⚠️ Example #{filename} exited with status #{exit_status}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
puts
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts
|
|
48
|
+
puts
|
|
49
|
+
puts "Completed running all #{example_files.size} examples"
|
|
50
|
+
puts
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Run all example DSL files in the examples directory
|
|
5
|
+
|
|
6
|
+
require 'pathname'
|
|
7
|
+
|
|
8
|
+
# Get the directory where this script is located
|
|
9
|
+
examples_dir = Pathname.new(__FILE__).dirname
|
|
10
|
+
|
|
11
|
+
# Find all Ruby files except this one
|
|
12
|
+
example_files = Dir.glob(examples_dir.join('*_dsl.rb'))
|
|
13
|
+
.reject { |f| File.basename(f).start_with? 'run_all' }
|
|
14
|
+
.sort
|
|
15
|
+
|
|
16
|
+
puts
|
|
17
|
+
puts "Running #{example_files.size} examples from #{examples_dir}"
|
|
18
|
+
puts
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
example_files.each_with_index do |file, index|
|
|
22
|
+
filename = File.basename(file)
|
|
23
|
+
filename_size = filename.size + 6
|
|
24
|
+
|
|
25
|
+
STDERR.puts "Running example #{index + 1}/#{example_files.size}: #{filename} ..."
|
|
26
|
+
|
|
27
|
+
puts
|
|
28
|
+
puts "=" * filename_size
|
|
29
|
+
puts "## #{filename} ##"
|
|
30
|
+
puts "=" * filename_size
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
# Run the example
|
|
34
|
+
system("ruby", file)
|
|
35
|
+
|
|
36
|
+
exit_status = $?.exitstatus
|
|
37
|
+
|
|
38
|
+
if exit_status != 0
|
|
39
|
+
puts
|
|
40
|
+
puts "⚠️ Example #{filename} exited with status #{exit_status}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
puts
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
puts
|
|
47
|
+
puts
|
|
48
|
+
puts "Completed running all #{example_files.size} examples"
|
|
49
|
+
puts
|