Class: Lutaml::Cli::OutputFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/lutaml/cli/output_formatter.rb

Overview

OutputFormatter provides formatting utilities for CLI output

Supports multiple output formats:

  • text: Human-readable text format

  • table: Tabular format for structured data

  • yaml: YAML format

  • json: JSON format

  • tree: Tree view for hierarchical data

Direct Known Subclasses

EnhancedFormatter

Constant Summary collapse

COLORS =

ANSI color codes for terminal output

{
  red: "\e[31m",
  green: "\e[32m",
  yellow: "\e[33m",
  blue: "\e[34m",
  magenta: "\e[35m",
  cyan: "\e[36m",
  reset: "\e[0m",
}.freeze

Class Method Summary collapse

Class Method Details

.colorize(text, color) ⇒ String

Colorize text for terminal output

Parameters:

  • text (String)

    Text to colorize

  • color (Symbol)

    Color name

Returns:

  • (String)

    Colorized text



288
289
290
291
292
# File 'lib/lutaml/cli/output_formatter.rb', line 288

def self.colorize(text, color)
  return text unless $stdout.tty?

  "#{COLORS[color]}#{text}#{COLORS[:reset]}"
end

.convert_to_plain_hash(data) ⇒ Hash

Convert data to a plain Ruby hash for serialization.

Parameters:

  • data (Object)

    Data to convert

Returns:

  • (Hash)

    Plain Ruby hash



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/lutaml/cli/output_formatter.rb', line 57

def self.convert_to_plain_hash(data) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
  return data unless data

  case data
  when Hash
    # Deep convert to plain hash
    data.transform_values do |value|
      convert_to_plain_hash(value)
    end
  when Array
    data.map { |item| convert_to_plain_hash(item) }
  when String, Integer, Float, TrueClass, FalseClass, NilClass
    data
  else
    # For complex objects, convert to string representation
    data.respond_to?(:name) ? data.name : data.to_s
  end
end

.error(message) ⇒ String

Format error message

Parameters:

  • message (String)

    Message to format

Returns:

  • (String)

    Formatted message



306
307
308
# File 'lib/lutaml/cli/output_formatter.rb', line 306

def self.error(message)
  colorize("#{message}", :red)
end

.format(data, format: "text") ⇒ String

Format and output data based on the specified format

Parameters:

  • data (Object)

    Data to format

  • format (String) (defaults to: "text")

    Output format (text, yaml, json, table)

Returns:

  • (String)

    Formatted output



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/lutaml/cli/output_formatter.rb', line 34

def self.format(data, format: "text") # rubocop:disable Metrics/MethodLength
  case format.to_s.downcase
  when "yaml"
    # Convert to plain hash if needed for proper YAML serialization
    plain_data = convert_to_plain_hash(data)
    plain_data.to_yaml
  when "json"
    # Convert to plain hash for proper JSON serialization
    plain_data = convert_to_plain_hash(data)
    JSON.pretty_generate(plain_data)
  when "table"
    format_table(data)
  when "text"
    format_text(data)
  else
    data.to_s
  end
end

.format_array_table(array, options: {}) ⇒ String

Format an array of hashes as a table using TableTennis

Parameters:

  • array (Array<Hash>)

    Array to format

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

    TableTennis options

Returns:

  • (String)

    Formatted table



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/lutaml/cli/output_formatter.rb', line 117

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

  # Default TableTennis options for lutaml
  # Set layout: false to prevent aggressive column shrinking
  default_options = {
    zebra: true,
    row_numbers: false,
    separators: true,
    titleize: true,
    layout: false, # Disable auto-layout to show full content
  }

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

.format_hash_table(hash) ⇒ String

Format a hash as a two-column table

Parameters:

  • hash (Hash)

    Hash to format

Returns:

  • (String)

    Formatted table



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/lutaml/cli/output_formatter.rb', line 96

def self.format_hash_table(hash) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  max_key_length = hash.keys.map(&:to_s).map(&:length).max || 0
  lines = []

  hash.each do |key, value|
    formatted_value = if value.is_a?(Hash) || value.is_a?(Array)
                        value.inspect
                      else
                        value.to_s
                      end
    lines << format("%-#{max_key_length}s : %s", key, formatted_value)
  end

  lines.join("\n")
end

.format_stats(stats, detailed: false) ⇒ String

Format statistics output

Parameters:

  • stats (Hash)

    Statistics hash

  • detailed (Boolean) (defaults to: false)

    Whether to show detailed stats

Returns:

  • (String)

    Formatted statistics



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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
# File 'lib/lutaml/cli/output_formatter.rb', line 199

def self.format_stats(stats, detailed: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  return "No statistics available" unless stats

  lines = []

  lines << colorize("Repository Statistics", :cyan)
  lines << ("=" * 50)
  lines << ""

  # Basic counts
  lines << colorize("Basic Counts:", :yellow)
  lines << "  Total Packages:    #{stats[:total_packages] || 0}"
  lines << "  Total Classes:     #{stats[:total_classes] || 0}"
  lines << "  Total Data Types:  #{stats[:total_data_types] || 0}"
  lines << "  Total Enums:       #{stats[:total_enums] || 0}"
  lines << "  Total Diagrams:    #{stats[:total_diagrams] || 0}"
  lines << ""

  # Package statistics
  lines << colorize("Package Statistics:", :yellow)
  lines << "  Max Depth:         #{stats[:max_package_depth] || 0}"
  lines << "  Avg Depth:         " \
           "#{'%.2f' % (stats[:avg_package_depth] || 0)}"

  if detailed && stats[:packages_by_depth]
    lines << "  Depth Distribution:"
    stats[:packages_by_depth].each do |depth, count|
      lines << "    Level #{depth}: #{count} package(s)"
    end
  end
  lines << ""

  # Class statistics
  lines << colorize("Class Statistics:", :yellow)
  lines << "  Total Attributes:   #{stats[:total_attributes] || 0}"
  lines << "  Total Associations: #{stats[:total_associations] || 0}"
  lines << "  Avg Complexity:     " \
           "#{'%.2f' % (stats[:avg_class_complexity] || 0)}"

  if detailed && stats[:classes_by_stereotype] &&
      !stats[:classes_by_stereotype].empty?
    lines << "  By Stereotype:"
    stats[:classes_by_stereotype].each do |stereotype, count|
      lines << "    #{stereotype}: #{count}"
    end
  end
  lines << ""

  # Inheritance statistics
  if stats[:total_inheritance_relationships]
    lines << colorize("Inheritance:", :yellow)
    lines << "  Relationships:     " \
             "#{stats[:total_inheritance_relationships]}"
    lines << "  Max Depth:         #{stats[:max_inheritance_depth]}"
    lines << ""
  end

  # Quality metrics
  if detailed
    lines << colorize("Quality Metrics:", :yellow)
    lines << "  Abstract Classes:  #{stats[:abstract_class_count] || 0}"
    lines << "  Undocumented:      " \
             "#{stats[:classes_without_documentation] || 0}"
    lines << "  Without Attrs:     " \
             "#{stats[:classes_without_attributes] || 0}"
    lines << ""
  end

  # Most complex classes
  if detailed && stats[:most_complex_classes] &&
      !stats[:most_complex_classes].empty?
    lines << colorize("Most Complex Classes:", :yellow)
    stats[:most_complex_classes].first(5).each do |item|
      klass = item[:class]
      complexity = item[:complexity]
      class_name = klass.respond_to?(:name) ? klass.name : klass.to_s

      lines << "  #{class_name} (complexity: #{complexity})"
    end
  end

  lines.join("\n")
end

.format_table(data) ⇒ String

Format data as a simple table

Parameters:

  • data (Hash, Array)

    Data to format

Returns:

  • (String)

    Formatted table



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/lutaml/cli/output_formatter.rb', line 80

def self.format_table(data)
  return "" if data.nil? || data.empty?

  if data.is_a?(Hash)
    format_hash_table(data)
  elsif data.is_a?(Array) && data.first.is_a?(Hash)
    format_array_table(data)
  else
    data.to_s
  end
end

.format_text(data) ⇒ String

Format data as text

Parameters:

  • data (Object)

    Data to format

Returns:

  • (String)

    Formatted text



138
139
140
141
142
143
144
145
146
147
# File 'lib/lutaml/cli/output_formatter.rb', line 138

def self.format_text(data)
  case data
  when Hash
    format_hash_table(data)
  when Array
    data.join("\n")
  else
    data.to_s
  end
end

.format_tree(node, prefix: "", is_last: true, show_counts: true) ⇒ String

Format a tree structure with optional metadata

:diagrams_count keys

Parameters:

  • node (Hash)

    Tree node with :name, :children, :classes_count,

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

    Prefix for indentation

  • is_last (Boolean) (defaults to: true)

    Whether this is the last child

  • show_counts (Boolean) (defaults to: true)

    Whether to show class/diagram counts

Returns:

  • (String)

    Formatted tree



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
# File 'lib/lutaml/cli/output_formatter.rb', line 157

def self.format_tree(node, prefix: "", is_last: true, show_counts: true) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  return "" if node.nil?

  lines = []
  connector = is_last ? "└── " : "├── "

  # Build node display with optional counts
  node_text = node[:name]
  if show_counts
     = []
    if node[:classes_count]&.positive?
       << "#{node[:classes_count]} classes"
    end
    if node[:diagrams_count]&.positive?
       << "#{node[:diagrams_count]} diagrams"
    end

    unless .empty?
      node_text += " (#{.join(', ')})"
    end
  end

  lines << "#{prefix}#{connector}#{node_text}"

  children = node[:children] || []
  children.each_with_index do |child, index|
    child_is_last = (index == children.size - 1)
    extension = is_last ? "    " : ""
    lines << format_tree(child,
                         prefix: prefix + extension,
                         is_last: child_is_last,
                         show_counts: show_counts)
  end

  lines.join("\n")
end

.info(message) ⇒ String

Format info message

Parameters:

  • message (String)

    Message to format

Returns:

  • (String)

    Formatted message



322
323
324
# File 'lib/lutaml/cli/output_formatter.rb', line 322

def self.info(message)
  colorize("#{message}", :blue)
end

.progress(message) ⇒ void

This method returns an undefined value.

Show progress indicator

Parameters:

  • message (String)

    Progress message



330
331
332
333
# File 'lib/lutaml/cli/output_formatter.rb', line 330

def self.progress(message)
  print colorize("#{message}...", :cyan)
  $stdout.flush
end

.progress_done(success: true) ⇒ void

This method returns an undefined value.

Complete progress indicator

Parameters:

  • success (Boolean) (defaults to: true)

    Whether operation succeeded



339
340
341
342
343
344
345
# File 'lib/lutaml/cli/output_formatter.rb', line 339

def self.progress_done(success: true)
  if success
    puts " #{colorize('', :green)}"
  else
    puts " #{colorize('', :red)}"
  end
end

.success(message) ⇒ String

Format success message

Parameters:

  • message (String)

    Message to format

Returns:

  • (String)

    Formatted message



298
299
300
# File 'lib/lutaml/cli/output_formatter.rb', line 298

def self.success(message)
  colorize("#{message}", :green)
end

.warning(message) ⇒ String

Format warning message

Parameters:

  • message (String)

    Message to format

Returns:

  • (String)

    Formatted message



314
315
316
# File 'lib/lutaml/cli/output_formatter.rb', line 314

def self.warning(message)
  colorize("#{message}", :yellow)
end