Class: Pvectl::Formatters::Table
- Defined in:
- lib/pvectl/formatters/table.rb,
sig/pvectl/formatters/table.rbs
Overview
Formats data as a table using tty-table gem.
For collections: renders standard horizontal table with headers. For single resources (describe): renders vertical key-value layout.
Instance Method Summary collapse
-
#colorize_row(row, headers, pastel) ⇒ Array
Colorizes status columns in a row.
-
#format(data, presenter, color_enabled: true, describe: false, **context) ⇒ String
Formats data as table output.
-
#format_describe(model, presenter, pastel) ⇒ String
Formats single resource as vertical key-value layout.
-
#format_describe_hash(hash, pastel, indent: 0) ⇒ String
Recursively formats a hash for describe output.
-
#format_describe_table(array, indent: 0) ⇒ String
Formats array of hashes as inline table.
-
#format_describe_value(value, key, pastel) ⇒ String
Formats a value for describe output.
-
#format_table(data, presenter, pastel, **context) ⇒ String
Formats collection as horizontal table.
-
#humanize_key(key) ⇒ String
Converts snake_case or kebab-case key to Title Case.
-
#render_table(headers, rows) ⇒ String
Renders table using tty-table.
Methods inherited from Base
Instance Method Details
#colorize_row(row, headers, pastel) ⇒ Array
Colorizes status columns in a row.
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/pvectl/formatters/table.rb', line 80 def colorize_row(row, headers, pastel) row.each_with_index.map do |value, idx| header = headers[idx].to_s.downcase if header == "status" ColorSupport.colorize_status(value, pastel) else normalize_nil(value) end end end |
#format(data, presenter, color_enabled: true, describe: false, **context) ⇒ String
Formats data as table output.
33 34 35 36 37 38 39 40 41 |
# File 'lib/pvectl/formatters/table.rb', line 33 def format(data, presenter, color_enabled: true, describe: false, **context) pastel = ColorSupport.pastel(explicit_flag: color_enabled) if describe || !collection?(data) format_describe(data, presenter, pastel) else format_table(data, presenter, pastel, **context) end end |
#format_describe(model, presenter, pastel) ⇒ String
Formats single resource as vertical key-value layout. Supports nested Hashes (sections) and Arrays of Hashes (tables).
69 70 71 72 |
# File 'lib/pvectl/formatters/table.rb', line 69 def format_describe(model, presenter, pastel) hash = presenter.to_description(model) format_describe_hash(hash, pastel, indent: 0) end |
#format_describe_hash(hash, pastel, indent: 0) ⇒ String
Recursively formats a hash for describe output.
At indent 0 (top level), once a section (Hash/Array) has been rendered, all subsequent entries get a blank line separator for consistent spacing.
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 |
# File 'lib/pvectl/formatters/table.rb', line 100 def format_describe_hash(hash, pastel, indent: 0) return "-" if hash.nil? lines = [] prefix = " " * indent # Calculate max key length for alignment (only for non-nested values) simple_keys = hash.select { |_, v| !v.is_a?(Hash) && !(v.is_a?(Array) && v.first.is_a?(Hash)) && !v.to_s.include?("\n") } max_key_length = simple_keys.keys.map { |k| k.to_s.length }.max || 0 # Track whether we've passed a nested section (Hash/Array). # Once a section appears, all subsequent simple entries get blank-line separators. prev_was_section = false hash.each do |key, value| human_key = humanize_key(key) if value.is_a?(Hash) # Nested section lines << "" lines << "#{prefix}#{human_key}:" lines << format_describe_hash(value, pastel, indent: indent + 1) prev_was_section = true elsif value.is_a?(Array) && !value.empty? && value.first.is_a?(Hash) # Array of hashes -> inline table lines << "" lines << "#{prefix}#{human_key}:" lines << format_describe_table(value, indent: indent + 1) prev_was_section = true elsif value.is_a?(Array) && value.empty? # Empty array -> show as "-" lines << "" if prev_was_section formatted_key = "#{human_key}:".ljust(max_key_length + 2) lines << "#{prefix}#{formatted_key}-" else formatted_value = format_describe_value(value, key.to_s, pastel) if formatted_value.include?("\n") # Multi-line value: render as block section lines << "" lines << "#{prefix}#{human_key}:" formatted_value.each_line do |line| lines << "#{prefix} #{line.chomp}" end prev_was_section = true else # Simple key-value — add separator after sections at any indent level lines << "" if prev_was_section formatted_key = "#{human_key}:".ljust(max_key_length + 2) lines << "#{prefix}#{formatted_key}#{formatted_value}" end end end lines.join("\n").sub(/\A\n+/, "").rstrip end |
#format_describe_table(array, indent: 0) ⇒ String
Formats array of hashes as inline table.
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/pvectl/formatters/table.rb', line 161 def format_describe_table(array, indent: 0) return " " * indent + "-" if array.empty? prefix = " " * indent headers = array.first.keys.map { |k| k.to_s.upcase } rows = array.map { |item| item.values.map(&:to_s) } # Calculate column widths widths = headers.each_with_index.map do |h, i| [h.length, *rows.map { |r| r[i]&.length || 0 }].max end lines = [] # Header header_line = headers.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ") lines << "#{prefix}#{header_line}" # Rows rows.each do |row| row_line = row.each_with_index.map { |v, i| (v || "-").ljust(widths[i]) }.join(" ") lines << "#{prefix}#{row_line}" end lines.join("\n") end |
#format_describe_value(value, key, pastel) ⇒ String
Formats a value for describe output.
193 194 195 196 197 198 199 200 201 |
# File 'lib/pvectl/formatters/table.rb', line 193 def format_describe_value(value, key, pastel) return "-" if value.nil? if key.downcase == "status" ColorSupport.colorize_status(value, pastel) else value.to_s end end |
#format_table(data, presenter, pastel, **context) ⇒ String
Formats collection as horizontal table.
52 53 54 55 56 57 58 59 60 |
# File 'lib/pvectl/formatters/table.rb', line 52 def format_table(data, presenter, pastel, **context) headers = presenter.columns rows = data.map do |model| row = presenter.to_row(model, **context) colorize_row(row, headers, pastel) end render_table(headers, rows) end |
#humanize_key(key) ⇒ String
Converts snake_case or kebab-case key to Title Case. Preserves already-formatted keys (e.g., "CPU", "DNS", "Cloud-Init").
208 209 210 211 212 213 214 215 |
# File 'lib/pvectl/formatters/table.rb', line 208 def humanize_key(key) str = key.to_s # If the key is already formatted (contains spaces, is all caps, # or has uppercase letters indicating intentional formatting), return as-is return str if str.include?(" ") || str == str.upcase || str.match?(/[A-Z]/) str.split(/[_-]/).map(&:capitalize).join(" ") end |
#render_table(headers, rows) ⇒ String
Renders table using tty-table.
Uses :basic renderer for clean kubectl-like output (no borders). Handles non-TTY environments gracefully by rescuing ioctl errors.
225 226 227 228 229 230 231 232 |
# File 'lib/pvectl/formatters/table.rb', line 225 def render_table(headers, rows) table = TTY::Table.new(header: headers, rows: rows) table.render(:basic, padding: [0, 2]) || headers.join("\t") rescue NoMethodError # TTY::Screen may call ioctl on non-TTY streams (e.g., StringIO in tests) # Fall back to simple tab-separated output ([headers] + rows).map { |row| row.join("\t") }.join("\n") end |