Class: RubynCode::CLI::LoopRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyn_code/cli/loop_runner.rb

Overview

Drives a repeating execution of a payload (a prompt or slash command).

Pure orchestration with injected dependencies so it can be unit-tested without a REPL: the caller supplies a ‘runner` callable that performs one iteration and a `sleeper` for the wait between iterations.

Two modes:

- interval mode (interval is a positive number of seconds): run, wait,
  run, ... until max_iterations or stopped.
- self-paced mode (interval is nil): run back-to-back until the runner
  returns :stop, emits the DONE_SENTINEL, or max_iterations is hit.

Constant Summary collapse

DEFAULT_MAX_ITERATIONS =
50
DONE_SENTINEL =
'LOOP_DONE'
UNITS =
{ 's' => 1, 'm' => 60, 'h' => 3600 }.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(runner:, interval: nil, max_iterations: DEFAULT_MAX_ITERATIONS, sleeper: nil, on_iteration: nil) ⇒ LoopRunner

Returns a new instance of LoopRunner.

Parameters:

  • interval (Integer, nil) (defaults to: nil)

    seconds between runs; nil => self-paced

  • runner (#call)

    ->(iteration_index) => String|:stop

  • max_iterations (Integer) (defaults to: DEFAULT_MAX_ITERATIONS)

    hard cap on iterations

  • sleeper (#call, nil) (defaults to: nil)

    ->(seconds); defaults to Kernel#sleep

  • on_iteration (#call, nil) (defaults to: nil)

    ->(n, total) UI callback before each run



43
44
45
46
47
48
49
50
51
# File 'lib/rubyn_code/cli/loop_runner.rb', line 43

def initialize(runner:, interval: nil, max_iterations: DEFAULT_MAX_ITERATIONS,
               sleeper: nil, on_iteration: nil)
  @interval = interval
  @runner = runner
  @max_iterations = max_iterations
  @sleeper = sleeper || ->(seconds) { sleep(seconds) }
  @on_iteration = on_iteration
  @stopped = false
end

Class Method Details

.parse_interval(token) ⇒ Integer?

Parse an interval token like “30s”, “5m”, “2h”, or a bare “45” (seconds). Returns nil when the token is not a valid interval.

Parameters:

  • token (String, nil)

Returns:

  • (Integer, nil)

    seconds, or nil



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/rubyn_code/cli/loop_runner.rb', line 26

def self.parse_interval(token)
  return nil if token.nil?

  match = token.strip.match(/\A(\d+)\s*([smh]?)\z/i)
  return nil unless match

  amount = match[1].to_i
  return nil if amount <= 0

  amount * UNITS.fetch(match[2].downcase, 1)
end

Instance Method Details

#runInteger

Run the loop.

Returns:

  • (Integer)

    number of iterations completed



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rubyn_code/cli/loop_runner.rb', line 65

def run
  completed = 0
  @max_iterations.times do |index|
    break if @stopped

    @on_iteration&.call(index + 1, @max_iterations)
    result = @runner.call(index)
    completed += 1

    break if @stopped || done?(result)

    wait_before_next(index)
  end
  completed
rescue Interrupt
  completed
end

#stop!void

This method returns an undefined value.

Request the loop to stop after the current iteration.



55
56
57
# File 'lib/rubyn_code/cli/loop_runner.rb', line 55

def stop!
  @stopped = true
end

#stopped?Boolean

Returns:

  • (Boolean)


60
# File 'lib/rubyn_code/cli/loop_runner.rb', line 60

def stopped? = @stopped