Module: Scjson::Engine
- Defined in:
- lib/scjson/engine.rb,
lib/scjson/engine/context.rb
Overview
Engine interface to emit standardized JSONL execution traces.
This is a contract-level stub that preserves the CLI and trace schema while the full runtime is being implemented. It mirrors Python flags and behavior where appropriate, following Ruby idioms.
Defined Under Namespace
Classes: DocumentContext
Class Method Summary collapse
-
.trace(input_path:, events_path: nil, out_path: nil, xml: false, leaf_only: false, omit_actions: false, omit_delta: false, omit_transitions: false, advance_time: 0.0, ordering: 'tolerant', max_steps: nil, strip_step0_noise: false, strip_step0_states: false, keep_cond: false, defer_done: true) ⇒ void
Emit a standardized JSONL trace for the given document and event stream.
Class Method Details
.trace(input_path:, events_path: nil, out_path: nil, xml: false, leaf_only: false, omit_actions: false, omit_delta: false, omit_transitions: false, advance_time: 0.0, ordering: 'tolerant', max_steps: nil, strip_step0_noise: false, strip_step0_states: false, keep_cond: false, defer_done: true) ⇒ void
This method returns an undefined value.
Emit a standardized JSONL trace for the given document and event stream.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/scjson/engine.rb', line 38 def trace(input_path:, events_path: nil, out_path: nil, xml: false, leaf_only: false, omit_actions: false, omit_delta: false, omit_transitions: false, advance_time: 0.0, ordering: 'tolerant', max_steps: nil, strip_step0_noise: false, strip_step0_states: false, keep_cond: false, defer_done: true) sink = out_path ? File.open(out_path, 'w', encoding: 'utf-8') : $stdout begin ctx = DocumentContext.from_file(input_path, xml: xml) begin ctx.ordering_mode = (ordering || 'tolerant') rescue StandardError # ignore if not supported end begin ctx.defer_done = !!defer_done rescue StandardError # ignore if not supported end leaves = leaf_only ? ctx.leaf_state_ids : nil # Step 0 snapshot init = ctx.trace_init if leaf_only && leaves %w[configuration enteredStates exitedStates].each do |k| init[k] = (init[k] || []).select { |sid| leaves.include?(sid) } end end init['actionLog'] = [] if omit_actions init['datamodelDelta'] = {} if omit_delta init['firedTransitions'] = [] if omit_transitions if strip_step0_noise init['datamodelDelta'] = {} init['firedTransitions'] = [] end if strip_step0_states init['enteredStates'] = [] init['exitedStates'] = [] end sink.write(JSON.generate({ step: 0 }.merge(init)) + "\n") # Stream of events: from file or STDIN stream = events_path ? File.open(events_path, 'r', encoding: 'utf-8') : $stdin # Apply global advance_time before first event if provided if advance_time && advance_time.to_f > 0 begin ctx.advance_time(advance_time.to_f) rescue StandardError # ignore end end step_no = 1 stream.each_line do |line| line = line.strip next if line.empty? begin msg = JSON.parse(line) rescue StandardError next end # Control token: advance_time -> skip trace emission, but flush timers if msg.is_a?(Hash) && msg.key?('advance_time') begin adv = msg['advance_time'] ctx.advance_time(adv.to_f) # After advancing time, flush any pending timers by running a synthetic step. # Only emit a step if something actually changed (entered/exited/fired). rec = ctx.trace_step(name: '__time__', data: nil) if leaf_only && leaves %w[configuration enteredStates exitedStates].each do |k| rec[k] = (rec[k] || []).select { |sid| leaves.include?(sid) } end end rec['event'] = nil # hide synthetic event name rec['actionLog'] = [] if omit_actions unless omit_delta if rec['datamodelDelta'].is_a?(Hash) dm = rec['datamodelDelta'] rec['datamodelDelta'] = dm.keys.sort.each_with_object({}) { |k, h| h[k] = dm[k] } end else rec['datamodelDelta'] = {} end unless keep_cond if rec['firedTransitions'].is_a?(Array) rec['firedTransitions'] = rec['firedTransitions'].map do |ft| if ft.is_a?(Hash) ft['cond'] = nil end ft end end end rec['firedTransitions'] = [] if omit_transitions sink.write(JSON.generate({ step: step_no }.merge(rec)) + "\n") step_no += 1 rescue StandardError # ignore malformed end next end break if max_steps && step_no > max_steps evt_name = (msg.is_a?(Hash) && (msg['event'] || msg['name'])) next unless evt_name evt_data = msg.is_a?(Hash) ? msg['data'] : nil rec = ctx.trace_step(name: evt_name.to_s, data: evt_data) if leaf_only && leaves %w[configuration enteredStates exitedStates].each do |k| rec[k] = (rec[k] || []).select { |sid| leaves.include?(sid) } end end rec['actionLog'] = [] if omit_actions # sort datamodelDelta keys for deterministic output unless omit_delta if rec['datamodelDelta'].is_a?(Hash) dm = rec['datamodelDelta'] rec['datamodelDelta'] = dm.keys.sort.each_with_object({}) { |k, h| h[k] = dm[k] } end else rec['datamodelDelta'] = {} end # scrub cond in firedTransitions unless requested unless keep_cond if rec['firedTransitions'].is_a?(Array) rec['firedTransitions'] = rec['firedTransitions'].map do |ft| if ft.is_a?(Hash) ft['cond'] = nil end ft end end end rec['firedTransitions'] = [] if omit_transitions sink.write(JSON.generate({ step: step_no }.merge(rec)) + "\n") step_no += 1 end ensure sink.close if sink && sink != $stdout end end |