Module: Phlex::Reactive::Inspector::Report

Defined in:
lib/phlex/reactive/inspector/report.rb

Overview

Renders the Inspector's read-only inventory for the phlex_reactive:actions and phlex_reactive:find rake tasks (issue #168). Plain text by default, JSON when asked — the SAME no-ANSI posture as Doctor (clean CI/log capture). Reports NAMES/paths/schemas only, never tokens/secrets/state.

Class Method Summary collapse

Class Method Details

.action_detail(action) ⇒ Object



80
81
82
83
84
85
86
# File 'lib/phlex/reactive/inspector/report.rb', line 80

def action_detail(action)
  header = "  action :#{action.name}"
  header += "  params: #{params_str(action.params)}" unless action.params.empty?
  header += "  (#{location_str(action.source_location)})  auth: #{auth_str(action)}"
  body = action.definition ? indent(action.definition) : "    (method not defined)"
  [header, body, ""]
end

.action_hash(action) ⇒ Object



108
109
110
111
112
113
114
115
# File 'lib/phlex/reactive/inspector/report.rb', line 108

def action_hash(action)
  {
    name: action.name,
    params: action.params,
    source_location: location_str(action.source_location),
    authorization_call_detected: action.authorization_call_detected?
  }
end

.actions(components, format: :text) ⇒ Object

A plain-text table of every component × action, or a JSON array when format is :json. One row per action: component, action, params, file:line, authorization heuristic.



18
19
20
21
22
# File 'lib/phlex/reactive/inspector/report.rb', line 18

def actions(components, format: :text)
  return actions_json(components) if format == :json

  actions_text(components)
end

.actions_json(components) ⇒ Object

-- json -------------------------------------------------------------



94
95
96
# File 'lib/phlex/reactive/inspector/report.rb', line 94

def actions_json(components)
  JSON.pretty_generate(components.map { component_hash(it) })
end

.actions_text(components) ⇒ Object

-- plain text -------------------------------------------------------



39
40
41
42
43
44
# File 'lib/phlex/reactive/inspector/report.rb', line 39

def actions_text(components)
  return "no reactive components found" if components.empty?

  rows = components.flat_map { component_rows(it) }
  render_table(%w[COMPONENT ACTION PARAMS FILE:LINE AUTH], rows)
end

.auth_str(action) ⇒ Object

The authorization heuristic as a short label — advisory only.



134
135
136
# File 'lib/phlex/reactive/inspector/report.rb', line 134

def auth_str(action)
  action.authorization_call_detected? ? "authorized*" : "unverified"
end

.component_hash(info) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/phlex/reactive/inspector/report.rb', line 98

def component_hash(info)
  {
    component: info.name,
    path: location_str(info.path),
    record_key: info.record_key,
    state_keys: info.state_keys,
    actions: info.actions.map { action_hash(it) }
  }
end

.component_rows(info) ⇒ Object



46
47
48
49
50
51
52
53
# File 'lib/phlex/reactive/inspector/report.rb', line 46

def component_rows(info)
  return [[info.name, "(no actions)", "", location_str(info.path), ""]] if info.actions.empty?

  info.actions.map do
    [info.name, it.name.to_s, params_str(it.params),
     location_str(it.source_location), auth_str(it)]
  end
end

.detail_lines(info) ⇒ Object

-- find detail ------------------------------------------------------



71
72
73
74
75
76
77
78
# File 'lib/phlex/reactive/inspector/report.rb', line 71

def detail_lines(info)
  lines = ["#{info.name}  (#{location_str(info.path)})"]
  lines << "  record: #{info.record_key}" if info.record_key
  lines << "  state:  #{info.state_keys.join(", ")}" if info.state_keys.any?
  lines << ""
  info.actions.each { lines.concat(action_detail(it)) }
  lines
end

.find(matches, query) ⇒ Object

The find result: a ranked list header, then the TOP match in detail — each action's params, source location, authorization heuristic, and the full method-definition source. Empty message when nothing matched.



27
28
29
30
31
32
33
34
35
# File 'lib/phlex/reactive/inspector/report.rb', line 27

def find(matches, query)
  return %(no component matched "#{query}") if matches.empty?

  lines = [%(#{matches.size} match#{"es" unless matches.size == 1} for "#{query}":)]
  matches.each { lines << "  #{it.name}" }
  lines << ""
  lines.concat(detail_lines(matches.first))
  lines.join("\n")
end

.format_row(row, widths) ⇒ Object



65
66
67
# File 'lib/phlex/reactive/inspector/report.rb', line 65

def format_row(row, widths)
  row.each_with_index.map { |cell, i| cell.to_s.ljust(widths[i]) }.join("  ").rstrip
end

.indent(text) ⇒ Object



88
89
90
# File 'lib/phlex/reactive/inspector/report.rb', line 88

def indent(text)
  text.each_line.map { "    #{it}" }.join.rstrip
end

.location_str(location) ⇒ Object

"file.rb:42", relative to Rails.root when possible. nil location (a declared-but-missing method, or an unlocatable const) prints "?".



125
126
127
128
129
130
131
# File 'lib/phlex/reactive/inspector/report.rb', line 125

def location_str(location)
  return "?" unless location

  file, line = location
  file = file.delete_prefix("#{::Rails.root}/") if defined?(::Rails) && ::Rails.root
  line ? "#{file}:#{line}" : file
end

.params_str(params) ⇒ Object

-- shared formatters ------------------------------------------------



119
120
121
# File 'lib/phlex/reactive/inspector/report.rb', line 119

def params_str(params)
  params.empty? ? "" : params.inspect
end

.render_table(headers, rows) ⇒ Object

A minimal fixed-column table: each column padded to its widest cell. No ANSI, no external dependency.



57
58
59
60
61
62
63
# File 'lib/phlex/reactive/inspector/report.rb', line 57

def render_table(headers, rows)
  all = [headers] + rows
  # Two nested blocks (column index + row) need distinct named params —
  # `it` would collide, so name both.
  widths = headers.each_index.map { |col| all.map { |row| row[col].to_s.length }.max } # rubocop:disable Style/ItBlockParameter
  all.map { format_row(it, widths) }.join("\n")
end