Class: Taski::Progress::Layout::Base

Inherits:
Execution::TaskObserver show all
Defined in:
lib/taski/progress/layout/base.rb

Overview

Base class for layout implementations. Layouts are responsible for:

  • Receiving events from ExecutionFacade (Observer pattern)
  • Managing task state tracking
  • Rendering templates using Liquid
  • Handling screen output

Observer Interface ===

ExecutionFacade Event Observer Method Theme Method
notify_ready on_ready (pulls root_task, output_capture)
notify_start on_start execution_start
notify_stop on_stop execution_complete/fail
notify_task_updated on_task_updated task_start/success/fail/skip/clean_*
notify_group_started on_group_started group_start
notify_group_completed on_group_completed group_success/fail

Direct Known Subclasses

Log, Simple, Tree::Event, Tree::Live

Instance Attribute Summary collapse

Attributes inherited from Execution::TaskObserver

#context

Instance Method Summary collapse

Constructor Details

#initialize(output: $stderr, theme: nil) ⇒ Base

Returns a new instance of Base.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/taski/progress/layout/base.rb', line 34

def initialize(output: $stderr, theme: nil)
  @output = output
  @context = nil
  @theme = theme || Theme::Default.new
  @theme_drop = ThemeDrop.new(@theme)
  @liquid_environment = build_liquid_environment
  @monitor = Monitor.new
  @tasks = {}
  @nest_level = 0
  @start_time = nil
  @root_task_class = nil
  @output_capture = nil
  @message_queue = []
  @group_start_times = {}
  @spinner_index = 0
  @spinner_timer = nil
  @spinner_running = false
  @active = false
end

Instance Attribute Details

#spinner_indexObject (readonly)

Returns the value of attribute spinner_index.



32
33
34
# File 'lib/taski/progress/layout/base.rb', line 32

def spinner_index
  @spinner_index
end

Instance Method Details

#on_group_completed(task_class, group_name, phase:, timestamp:) ⇒ Object

Event 6: Group completed within a task.

Parameters:

  • task_class (Class)
  • group_name (String)
  • phase (Symbol)

    :run or :clean

  • timestamp (Time)


133
134
135
136
137
138
139
# File 'lib/taski/progress/layout/base.rb', line 133

def on_group_completed(task_class, group_name, phase:, timestamp:)
  @monitor.synchronize do
    started_at = @group_start_times.delete([task_class, group_name])
    duration = started_at ? ((timestamp - started_at) * 1000).round : nil
    handle_group_completed(task_class, group_name, phase, duration)
  end
end

#on_group_started(task_class, group_name, phase:, timestamp:) ⇒ Object

Event 5: Group started within a task.

Parameters:

  • task_class (Class)
  • group_name (String)
  • phase (Symbol)

    :run or :clean

  • timestamp (Time)


121
122
123
124
125
126
# File 'lib/taski/progress/layout/base.rb', line 121

def on_group_started(task_class, group_name, phase:, timestamp:)
  @monitor.synchronize do
    @group_start_times[[task_class, group_name]] = timestamp
    handle_group_started(task_class, group_name, phase)
  end
end

#on_readyObject

Event 1: Facade is ready (root task set, output capture available). Pulls root_task_class and output_capture from context. Only sets root_task_class once (prevents nested executor overwrite).



59
60
61
62
63
64
65
66
# File 'lib/taski/progress/layout/base.rb', line 59

def on_ready
  @monitor.synchronize do
    return if @root_task_class
    @root_task_class = @context&.root_task_class
    @output_capture = @context&.output_capture
    handle_ready
  end
end

#on_startObject

Event 2: Start progress display. Increments nest level for nested executor support.



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/taski/progress/layout/base.rb', line 70

def on_start
  should_start = false
  @monitor.synchronize do
    @nest_level += 1
    return if @nest_level > 1
    return unless should_activate?
    @start_time = Time.now
    @active = true
    should_start = true
  end

  handle_start if should_start
end

#on_stopObject

Event 3: Stop progress display. Only finalizes when nest level reaches 0 and layout was actually activated.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/taski/progress/layout/base.rb', line 86

def on_stop
  should_stop = false
  was_active = false
  @monitor.synchronize do
    @nest_level -= 1 if @nest_level > 0
    return unless @nest_level == 0
    was_active = @active
    @active = false
    should_stop = true
  end

  return unless should_stop
  handle_stop if was_active
  flush_queued_messages
end

#on_task_updated(task_class, previous_state:, current_state:, phase:, timestamp:) ⇒ Object

Event 4: Task state transition.

Parameters:

  • task_class (Class)
  • previous_state (Symbol, nil)
  • current_state (Symbol)
  • phase (Symbol)

    :run or :clean

  • timestamp (Time)


108
109
110
111
112
113
114
# File 'lib/taski/progress/layout/base.rb', line 108

def on_task_updated(task_class, previous_state:, current_state:, phase:, timestamp:)
  @monitor.synchronize do
    progress = @tasks[task_class] ||= new_task_progress
    apply_state_transition(progress, current_state, phase, timestamp)
    handle_task_update(task_class, current_state, phase)
  end
end

#queue_message(text) ⇒ Object

Queue a message to be displayed after progress display stops Thread-safe for concurrent task execution

Parameters:

  • text (String)

    The message text to queue



160
161
162
# File 'lib/taski/progress/layout/base.rb', line 160

def queue_message(text)
  @monitor.synchronize { @message_queue << text }
end

#render_template_string(template_string, state: nil, task: nil, execution: nil, **variables) ⇒ String

Render a Liquid template string with the given variables. Uses scoped Liquid environment with ColorFilter and SpinnerTag.

Parameters:

  • template_string (String)

    Liquid template string

  • state (Symbol, nil) (defaults to: nil)

    State for icon tag

  • task (TaskDrop, nil) (defaults to: nil)

    Task drop

  • execution (ExecutionDrop, nil) (defaults to: nil)

    Execution drop

Returns:

  • (String)

    Rendered output



172
173
174
175
176
177
# File 'lib/taski/progress/layout/base.rb', line 172

def render_template_string(template_string, state: nil, task: nil, execution: nil, **variables)
  context_vars = build_context_vars(task:, execution:, **variables)
  template = Liquid::Template.parse(template_string, environment: @liquid_environment)
  template.assigns["state"] = state
  template.render(context_vars)
end

#start_spinner_timerObject

Start the spinner animation timer. Increments spinner_index at the template's spinner_interval.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/taski/progress/layout/base.rb', line 181

def start_spinner_timer
  @monitor.synchronize do
    return if @spinner_running
    @spinner_running = true
  end

  @spinner_timer = Thread.new do
    loop do
      running = @monitor.synchronize { @spinner_running }
      break unless running
      sleep @theme.spinner_interval
      @monitor.synchronize do
        @spinner_index = (@spinner_index + 1) % @theme.spinner_frames.size
      end
    end
  end
end

#stop_spinner_timerObject

Stop the spinner animation timer.



200
201
202
203
204
# File 'lib/taski/progress/layout/base.rb', line 200

def stop_spinner_timer
  @monitor.synchronize { @spinner_running = false }
  @spinner_timer&.join
  @spinner_timer = nil
end

#task_registered?(task_class) ⇒ Boolean

Check if a task is registered

Parameters:

  • task_class (Class)

    The task class to check

Returns:

  • (Boolean)

    true if the task is registered



144
145
146
# File 'lib/taski/progress/layout/base.rb', line 144

def task_registered?(task_class)
  @monitor.synchronize { @tasks.key?(task_class) }
end

#task_state(task_class) ⇒ Symbol?

Get the current state of a task

Parameters:

  • task_class (Class)

    The task class

Returns:

  • (Symbol, nil)

    The task state or nil if not registered



151
152
153
154
155
# File 'lib/taski/progress/layout/base.rb', line 151

def task_state(task_class)
  p = @monitor.synchronize { @tasks[task_class] }
  return nil unless p
  p[:clean_state] || p[:run_state]
end