Module: Harnex::TerminalStatus

Defined in:
lib/harnex/terminal_status.rb

Class Method Summary collapse

Class Method Details

.blank_to_nil(value) ⇒ Object



197
198
199
200
# File 'lib/harnex/terminal_status.rb', line 197

def blank_to_nil(value)
  text = value.to_s
  text.empty? ? nil : text
end

.build_from_history(record, fallback_repo_root) ⇒ Object



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
# File 'lib/harnex/terminal_status.rb', line 156

def build_from_history(record, fallback_repo_root)
  status = record["status"].to_s
  state =
    case status
    when "completed"
      "completed"
    when "failed", "timeout", "killed"
      "failed"
    else
      "unknown"
    end
  {
    "id" => record["id"].to_s,
    "repo_root" => fallback_repo_root,
    "state" => state,
    "terminal" => state != "unknown",
    "task_complete" => record["terminal_event"].to_s == "task_complete",
    "exit" => history_exit(status),
    "exit_code" => nil,
    "summary_out" => blank_to_nil(record["summary_out_path"]),
    "started_at" => record["started_at"],
    "ended_at" => record["ended_at"],
    "source" => "dispatch_history"
  }
end

.build_from_summary(record, summary_path, fallback_repo_root) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/harnex/terminal_status.rb', line 126

def build_from_summary(record, summary_path, fallback_repo_root)
  meta = record["meta"] || {}
  actual = record["actual"] || {}
  state = classify_summary_state(actual)
  {
    "id" => meta["id"].to_s,
    "repo_root" => meta["repo"] || fallback_repo_root,
    "state" => state,
    "terminal" => state != "unknown",
    "task_complete" => !!actual["task_complete"],
    "exit" => blank_to_nil(actual["exit"]),
    "exit_code" => actual["exit_code"],
    "summary_out" => summary_path,
    "started_at" => meta["started_at"],
    "ended_at" => meta["ended_at"],
    "source" => "summary_out"
  }
end

.classify_summary_state(actual) ⇒ Object



145
146
147
148
149
150
151
152
153
154
# File 'lib/harnex/terminal_status.rb', line 145

def classify_summary_state(actual)
  exit = actual["exit"].to_s
  exit_code = actual["exit_code"]

  return "completed" if exit == "success"
  return "completed" if exit.empty? && exit_code == 0
  return "failed" unless exit.empty? && exit_code.nil?

  "unknown"
end

.history_exit(status) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/harnex/terminal_status.rb', line 182

def history_exit(status)
  case status
  when "completed"
    "success"
  when "timeout"
    "timeout"
  when "killed"
    "killed"
  when "failed"
    "failure"
  else
    nil
  end
end

.history_paths(repo_root) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/harnex/terminal_status.rb', line 56

def history_paths(repo_root)
  local_path = DispatchHistory.path_for(repo_root)
  return [local_path] if File.file?(local_path)

  global_path = DispatchHistory.global_path
  return [global_path] if File.file?(global_path)

  []
rescue StandardError
  []
end

.history_record?(record) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/harnex/terminal_status.rb', line 94

def history_record?(record)
  record["schema_version"] == 1 && record.key?("status")
end

.history_time(record) ⇒ Object



116
117
118
# File 'lib/harnex/terminal_status.rb', line 116

def history_time(record)
  parse_time(record["ended_at"]) || parse_time(record["started_at"]) || Time.at(0)
end

.newer_history?(candidate, current) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
110
# File 'lib/harnex/terminal_status.rb', line 105

def newer_history?(candidate, current)
  return false unless candidate
  return true unless current

  history_time(candidate) >= history_time(current)
end

.newer_summary?(candidate, current) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
# File 'lib/harnex/terminal_status.rb', line 98

def newer_summary?(candidate, current)
  return false unless candidate
  return true unless current

  summary_time(candidate) >= summary_time(current)
end

.parse_time(value) ⇒ Object



120
121
122
123
124
# File 'lib/harnex/terminal_status.rb', line 120

def parse_time(value)
  Time.iso8601(value.to_s)
rescue ArgumentError
  nil
end

.resolve(id:, repo_root: Dir.pwd) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/harnex/terminal_status.rb', line 8

def resolve(id:, repo_root: Dir.pwd)
  normalized_id = Harnex.normalize_id(id)
  root = File.expand_path(repo_root.to_s.empty? ? Dir.pwd : repo_root)

  latest_summary = nil
  latest_summary_path = nil
  latest_history = nil

  history_paths(root).each do |path|
    summary, history = scan_dispatch_path(path, normalized_id)
    if newer_summary?(summary, latest_summary)
      latest_summary = summary
      latest_summary_path = path
    end
    latest_history = history if newer_history?(history, latest_history)
  end

  summary_path = latest_history && latest_history["summary_out_path"].to_s.strip
  if summary_path && !summary_path.empty? && File.file?(summary_path)
    summary, = scan_dispatch_path(summary_path, normalized_id)
    if newer_summary?(summary, latest_summary)
      latest_summary = summary
      latest_summary_path = summary_path
    end
  end

  return build_from_summary(latest_summary, latest_summary_path, root) if latest_summary
  return build_from_history(latest_history, root) if latest_history

  nil
end

.scan_dispatch_path(path, id) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/harnex/terminal_status.rb', line 68

def scan_dispatch_path(path, id)
  summary_record = nil
  history_record = nil

  File.foreach(path) do |line|
    record = JSON.parse(line)
    next unless record.is_a?(Hash)

    if summary_record?(record) && record.dig("meta", "id").to_s == id
      summary_record = record
    elsif history_record?(record) && record["id"].to_s == id
      history_record = record
    end
  rescue JSON::ParserError
    next
  end

  [summary_record, history_record]
rescue Errno::ENOENT
  [nil, nil]
end

.summary_record?(record) ⇒ Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/harnex/terminal_status.rb', line 90

def summary_record?(record)
  record["meta"].is_a?(Hash) && record["actual"].is_a?(Hash)
end

.summary_time(record) ⇒ Object



112
113
114
# File 'lib/harnex/terminal_status.rb', line 112

def summary_time(record)
  parse_time(record.dig("meta", "ended_at")) || parse_time(record.dig("meta", "started_at")) || Time.at(0)
end

.unknown(id:, repo_root: Dir.pwd) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/harnex/terminal_status.rb', line 40

def unknown(id:, repo_root: Dir.pwd)
  {
    "id" => Harnex.normalize_id(id),
    "repo_root" => File.expand_path(repo_root.to_s.empty? ? Dir.pwd : repo_root),
    "state" => "unknown",
    "terminal" => false,
    "task_complete" => false,
    "exit" => nil,
    "exit_code" => nil,
    "summary_out" => nil,
    "started_at" => nil,
    "ended_at" => nil,
    "source" => "none"
  }
end