Class: Lutaml::Cli::EnhancedFormatter

Inherits:
OutputFormatter show all
Defined in:
lib/lutaml/cli/enhanced_formatter.rb

Overview

EnhancedFormatter provides advanced formatting capabilities with icons, pagination, and interactive features

Features:

  • Tree views with Unicode icons

  • Paginated tables for large datasets

  • Enhanced class details with visual formatting

  • Interactive navigation for paginated content

Constant Summary collapse

ICONS =

Unicode icons for different element types

{
  package: "📦",
  class: "📋",
  enum: "🔢",
  datatype: "📊",
  diagram: "🖼️",
  favorite: "",
  complex: "🔥",
  attribute: "🔹",
  operation: "⚙️",
  association: "🔗",
  inheritance: "⬆️",
  folder: "📁",
  file: "📄",
}.freeze
TREE_CHARS =

Tree drawing characters

{
  vertical: "",
  horizontal: "",
  branch: "",
  corner: "",
  tee: "",
}.freeze

Constants inherited from OutputFormatter

OutputFormatter::COLORS

Class Method Summary collapse

Methods inherited from OutputFormatter

colorize, convert_to_plain_hash, error, format, format_array_table, format_hash_table, format_stats, format_table, format_text, format_tree, info, progress, progress_done, success, warning

Class Method Details

.build_metadata_string(node, config) ⇒ String

Build metadata string for tree node

Parameters:

  • node (Hash)

    Node with metadata

  • config (Hash)

    Display configuration

Returns:

  • (String)

    Formatted metadata



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 306

def self.(node, config) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
  parts = []

  if config[:show_counts] && node[:count]
    parts << " (#{node[:count]} classes)"
  end

  if config[:show_complexity] && node[:complexity] &&
      (node[:complexity] > 10)
    parts << " #{ICONS[:complex]}"
  end

  if node[:favorite]
    parts << " #{ICONS[:favorite]}"
  end

  parts.join
end

.calculate_column_widths(headers, rows) ⇒ Array<Integer>

Calculate column widths for table

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    Data rows

Returns:

  • (Array<Integer>)

    Column widths



429
430
431
432
433
434
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 429

def self.calculate_column_widths(headers, rows)
  headers.each_with_index.map do |header, i|
    max_content = rows.map { |row| row[i].to_s.length }.max || 0
    [header.to_s.length, max_content].max
  end
end

.clear_screenvoid

This method returns an undefined value.

Clear the terminal screen



469
470
471
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 469

def self.clear_screen
  print "\e[2J\e[H"
end

.format_box(text, width: 80) ⇒ String

Format a box around text

Parameters:

  • text (String)

    Text to box

  • width (Integer) (defaults to: 80)

    Box width

Returns:

  • (String)

    Boxed text



207
208
209
210
211
212
213
214
215
216
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 207

def self.format_box(text, width: 80)
  lines = []
  lines << "#{'' * (width - 2)}"
  text.split("\n").each do |line|
    padded = line.ljust(width - 4)
    lines << "#{padded}"
  end
  lines << "#{'' * (width - 2)}"
  lines.join("\n")
end

.format_cardinality(attr) ⇒ String

Format cardinality for display

Parameters:

  • attr (Object)

    Attribute with cardinality

Returns:

  • (String)

    Formatted cardinality



487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 487

def self.format_cardinality(attr) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  return "" unless attr.respond_to?(:cardinality)
  return "" unless attr.cardinality

  card = attr.cardinality
  if card.respond_to?(:min) && card.respond_to?(:max)
    min = card.min || "0"
    max = card.max || "*"
    "[#{min}..#{max}]"
  else
    ""
  end
end

.format_class_details_enhanced(klass, path_formatter = nil) ⇒ String

Format class details with enhanced visual formatting

Parameters:

  • klass (Object)

    Class object to display

  • path_formatter (Proc) (defaults to: nil)

    Formatter for package paths

Returns:

  • (String)

    Enhanced class details



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
155
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 128

def self.format_class_details_enhanced(klass, path_formatter = nil) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  lines = []

  # Header box
  lines << "#{'' * 78}"
  class_name = "#{ICONS[:class]} #{klass.name}"
  lines << "#{class_name.ljust(76)}"
  lines << "#{'' * 78}"
  lines << ""

  # Basic information
  lines << colorize("Basic Information:", :cyan)
  if klass.respond_to?(:xmi_id)
    lines << "  XMI ID:      #{klass.xmi_id}"
  end
  if klass.respond_to?(:stereotype) && klass.stereotype
    lines << "  Stereotype:  #{klass.stereotype}"
  end
  if klass.respond_to?(:is_abstract)
    lines << "  Abstract:    #{klass.is_abstract ? 'Yes' : 'No'}"
  end
  if path_formatter && klass.respond_to?(:package)
    package_path = path_formatter.call(klass.package)
    lines << "  Package:     #{ICONS[:package]} #{package_path}"
  end
  lines << ""

  # Attributes
  if klass.respond_to?(:attributes) && klass.attributes &&
      !klass.attributes.empty?
    lines << colorize(
      "#{ICONS[:attribute]} Attributes (#{klass.attributes.size}):",
      :yellow,
    )
    attr_data = klass.attributes.map do |attr|
      {
        name: attr.name,
        type: attr.type || "Unknown",
        cardinality: format_cardinality(attr),
      }
    end
    lines << indent_text(format_array_table(attr_data), 2)
    lines << ""
  end

  # Operations
  if klass.respond_to?(:operations) && klass.operations &&
      !klass.operations.empty?
    lines << colorize(
      "#{ICONS[:operation]} Operations (#{klass.operations.size}):",
      :yellow,
    )
    klass.operations.each do |op|
      params = if op.respond_to?(:parameters) && op.parameters
                 op.parameters.map do |p|
                   "#{p.name}: #{p.type}"
                 end.join(", ")
               else
                 ""
               end
      return_type = if op.respond_to?(:return_type) && op.return_type
                      " : #{op.return_type}"
                    else
                      ""
                    end
      lines << "  #{ICONS[:operation]} #{op.name}(#{params})" \
               "#{return_type}"
    end
    lines << ""
  end

  lines.join("\n")
end

.format_simple_table(headers, rows, options: {}) ⇒ String

Format a simple table without pagination using TableTennis

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    Data rows

  • options (Hash) (defaults to: {})

    TableTennis options

Returns:

  • (String)

    Formatted table



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 331

def self.format_simple_table(headers, rows, options: {}) # rubocop:disable Metrics/MethodLength
  return "" if rows.empty?

  # Convert to array of hashes for TableTennis
  data = rows.map do |row|
    headers.each_with_index.to_h { |h, i| [h.to_sym, row[i]] }
  end

  # Default options with zebra and separators
  default_options = {
    zebra: true,
    separators: true,
    titleize: false,
  }

  table_options = default_options.merge(options)
  TableTennis.new(data, table_options).to_s
end

.format_stats_enhanced(stats, options = {}) ⇒ String

Format statistics with visual enhancements

Parameters:

  • stats (Hash)

    Statistics data

  • options (Hash) (defaults to: {})

    Display options

Returns:

  • (String)

    Formatted statistics



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 223

def self.format_stats_enhanced(stats, options = {}) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  lines = []

  # Header
  lines << colorize("#{'' * 78}", :cyan)
  header = "Repository Statistics"
  padding = (78 - header.length) / 2
  lines << colorize(
    "#{' ' * padding}#{header}#{' ' * (78 - padding - header.length)}",
    :cyan,
  )
  lines << colorize("#{'' * 78}", :cyan)
  lines << ""

  # Package stats
  lines << colorize("#{ICONS[:package]} Packages", :yellow)
  lines << "  Total:        #{stats[:total_packages]}"
  lines << "  Max Depth:    #{stats[:max_package_depth]}"
  lines << "  Avg Depth:    #{'%.2f' % stats[:avg_package_depth]}"
  lines << ""

  # Class stats
  lines << colorize("#{ICONS[:class]} Classes", :yellow)
  lines << "  Total:        #{stats[:total_classes]}"
  lines << "  Data Types:   #{stats[:total_data_types]}"
  lines << "  Enumerations: #{stats[:total_enums]}"
  lines << "  Attributes:   #{stats[:total_attributes]}"
  lines << "  Operations:   #{stats[:total_operations] || 0}"
  lines << ""

  # Relationships
  lines << colorize("#{ICONS[:association]} Relationships", :yellow)
  lines << "  Associations: #{stats[:total_associations]}"
  if stats[:total_inheritance_relationships]
    lines << "  Inheritance:  #{stats[:total_inheritance_relationships]}"
    lines << "  Max Depth:    #{stats[:max_inheritance_depth]}"
  end
  lines << ""

  # Diagrams
  if stats[:total_diagrams]&.positive?
    lines << colorize("#{ICONS[:diagram]} Diagrams", :yellow)
    lines << "  Total:        #{stats[:total_diagrams]}"
    lines << ""
  end

  # Complexity indicators
  if options[:show_complexity] && stats[:avg_class_complexity]
    lines << colorize("#{ICONS[:complex]} Complexity Metrics", :yellow)
    lines << "  Avg Complexity: #{'%.2f' % stats[:avg_class_complexity]}"
    if stats[:most_complex_classes] &&
        !stats[:most_complex_classes].empty?
      lines << "  Most Complex:"
      stats[:most_complex_classes].first(3).each do |cls|
        complexity_icon = if cls[:total_complexity] > 10
                            ICONS[:complex]
                          else
                            ICONS[:class]
                          end
        lines << "    #{complexity_icon} #{cls[:name]} " \
                 "(#{cls[:total_complexity]})"
      end
    end
  end

  lines.join("\n")
end

.format_table_content(headers, rows, col_widths) ⇒ String

Format table content with headers and rows

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    Data rows

  • col_widths (Array<Integer>)

    Column widths

Returns:

  • (String)

    Formatted table



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 442

def self.format_table_content(headers, rows, col_widths) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  lines = []

  # Header row
  header_line = headers.each_with_index.map do |header, i|
    header.to_s.ljust(col_widths[i])
  end.join("")
  lines << header_line

  # Separator
  separator = col_widths.map { |w| "" * w }.join("─┼─")
  lines << separator

  # Data rows
  rows.each do |row|
    data_line = headers.each_with_index.map do |_, i|
      row[i].to_s.ljust(col_widths[i])
    end.join("")
    lines << data_line
  end

  lines.join("\n")
end

.format_table_with_pagination(headers, rows, options = {}) ⇒ String

Format a table with pagination support

(default: 50)

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    Data rows

  • options (Hash) (defaults to: {})

    Pagination options

Options Hash (options):

  • :page_size (Integer)

    Number of rows per page

  • :interactive (Boolean)

    Enable interactive navigation

  • :current_page (Integer)

    Starting page (default: 1)

Returns:

  • (String)

    Formatted and optionally paginated table



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 108

def self.format_table_with_pagination(headers, rows, options = {})
  page_size = options[:page_size] || 50
  interactive = options[:interactive] != false
  current_page = options[:current_page] || 1

  return format_simple_table(headers, rows) if rows.size <= page_size

  if interactive && $stdout.tty?
    interactive_paginated_table(headers, rows, page_size, current_page)
  else
    non_interactive_paginated_table(headers, rows, page_size,
                                    current_page)
  end
end

.format_tree_with_icons(node, config = {}, prefix: "", is_last: true) ⇒ String

Format a tree structure with icons and metadata

Parameters:

  • node (Hash)

    Tree node with :name, :type, :metadata, :children

  • config (Hash) (defaults to: {})

    Configuration options

  • prefix (String) (defaults to: "")

    Current indentation prefix

  • is_last (Boolean) (defaults to: true)

    Whether this is the last sibling

Options Hash (config):

  • :show_icons (Boolean)

    Display icons

  • :show_counts (Boolean)

    Display counts in metadata

  • :show_complexity (Boolean)

    Highlight complex nodes

Returns:

  • (String)

    Formatted tree with icons



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 54

def self.format_tree_with_icons( # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  node, config = {}, prefix: "", is_last: true
)
  return "" if node.nil?

  config = {
    show_icons: true,
    show_counts: true,
    show_complexity: false,
  }.merge(config)

  lines = []
  connector = if is_last
                "#{TREE_CHARS[:corner]}#{TREE_CHARS[:horizontal]}" \
                  "#{TREE_CHARS[:horizontal]} "
              else
                "#{TREE_CHARS[:branch]}#{TREE_CHARS[:horizontal]}" \
                  "#{TREE_CHARS[:horizontal]} "
              end

  # Build node display
  icon = config[:show_icons] ? get_icon(node) : ""
  name = node[:name] || "unknown"
   = (node, config)

  node_text = "#{icon}#{name}#{}"
  lines << "#{prefix}#{connector}#{node_text}"

  # Process children
  children = node[:children] || []
  children.each_with_index do |child, index|
    child_is_last = (index == children.size - 1)
    extension = is_last ? "    " : "#{TREE_CHARS[:vertical]}   "
    lines << format_tree_with_icons(
      child,
      config,
      prefix: prefix + extension,
      is_last: child_is_last,
    )
  end

  lines.join("\n")
end

.get_icon(node) ⇒ String

Get appropriate icon for a node

Parameters:

  • node (Hash)

    Node with :type key

Returns:

  • (String)

    Icon with trailing space



295
296
297
298
299
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 295

def self.get_icon(node)
  icon_key = node[:type]&.to_sym || :package
  icon = ICONS[icon_key] || ICONS[:package]
  "#{icon} "
end

.indent_text(text, spaces) ⇒ String

Indent text by a number of spaces

Parameters:

  • text (String)

    Text to indent

  • spaces (Integer)

    Number of spaces

Returns:

  • (String)

    Indented text



478
479
480
481
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 478

def self.indent_text(text, spaces)
  indent = " " * spaces
  text.split("\n").map { |line| "#{indent}#{line}" }.join("\n")
end

.interactive_paginated_table(headers, rows, page_size, current_page) ⇒ String

Interactive paginated table navigation

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    All data rows

  • page_size (Integer)

    Rows per page

  • current_page (Integer)

    Starting page

Returns:

  • (String)

    Final page output



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 357

def self.interactive_paginated_table( # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
  headers, rows, page_size, current_page
)
  total_pages = (rows.size.to_f / page_size).ceil
  page = current_page

  loop do
    clear_screen
    page_rows = rows[(page - 1) * page_size, page_size]
    col_widths = calculate_column_widths(headers, page_rows)

    puts format_table_content(headers, page_rows, col_widths)
    puts ""
    puts colorize(
      "Page #{page}/#{total_pages} (#{rows.size} total rows)", :cyan
    )
    puts ""
    puts "Navigation: [N]ext, [P]revious, [J]ump, [Q]uit"
    print "> "

    input = $stdin.gets&.chomp&.downcase
    case input
    when "n", ""
      page = [page + 1, total_pages].min
    when "p"
      page = [page - 1, 1].max
    when "j"
      print "Jump to page (1-#{total_pages}): "
      jump_page = $stdin.gets&.chomp&.to_i
      page = [[jump_page, 1].max, total_pages].min
    when "q"
      break
    end
  end

  # Return final page content
  page_rows = rows[(page - 1) * page_size, page_size]
  col_widths = calculate_column_widths(headers, page_rows)
  format_table_content(headers, page_rows, col_widths)
end

.non_interactive_paginated_table(headers, rows, page_size, current_page) ⇒ String

Non-interactive paginated table

Parameters:

  • headers (Array<String>)

    Column headers

  • rows (Array<Array>)

    All data rows

  • page_size (Integer)

    Rows per page

  • current_page (Integer)

    Page to display

Returns:

  • (String)

    Formatted page



405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/lutaml/cli/enhanced_formatter.rb', line 405

def self.non_interactive_paginated_table( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  headers, rows, page_size,
  current_page
)
  total_pages = (rows.size.to_f / page_size).ceil
  page = [[current_page, 1].max, total_pages].min

  page_rows = rows[(page - 1) * page_size, page_size]
  col_widths = calculate_column_widths(headers, page_rows)

  output = []
  output << format_table_content(headers, page_rows, col_widths)
  output << ""
  output << colorize(
    "Page #{page}/#{total_pages} (#{rows.size} total rows)", :cyan
  )
  output.join("\n")
end