Module: MixinBot::CLIOutput

Included in:
CLI, UtilsCLI
Defined in:
lib/mixin_bot/cli/output.rb

Overview

Structured and human-friendly stdout/stderr formatting for mixinbot.

Constant Summary collapse

OUTPUT_FORMATS =
%w[pretty json yaml].freeze

Instance Method Summary collapse

Instance Method Details

#abort_with_error(message, kind: nil, hint: nil, exception: nil) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/mixin_bot/cli/output.rb', line 74

def abort_with_error(message, kind: nil, hint: nil, exception: nil)
  resolved_kind = kind || (exception && CLIErrors.kind_for_exception(exception)) || CLIErrors.kind_for_message(message)
  hint ||= default_error_hint

  if structured_output?
    error_body = {
      'status' => 'error',
      'error' => {
        'kind' => resolved_kind.to_s,
        'message' => message.to_s
      }
    }
    error_body['error']['hint'] = hint if hint.present?
    warn(JSON.generate(error_body))
  else
    warn(format_error(message))
  end
  exit(1)
end

#cli_optionsObject



13
14
15
16
17
# File 'lib/mixin_bot/cli/output.rb', line 13

def cli_options
  merged = options.dup
  merged = parent_options.merge(merged) if respond_to?(:parent_options) && parent_options.present?
  merged
end

#current_command_nameObject



33
34
35
# File 'lib/mixin_bot/cli/output.rb', line 33

def current_command_name
  @current_command_name || self.class.name.split('::').last.sub(/CLI\z/, '').downcase
end

#emit_info(message) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/mixin_bot/cli/output.rb', line 64

def emit_info(message)
  return if message.blank?

  if structured_output?
    warn(message)
  else
    warn(format_info(message))
  end
end

#emit_list(items:, total:, limit:, offset:, command: 'list') ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/mixin_bot/cli/output.rb', line 54

def emit_list(items:, total:, limit:, offset:, command: 'list')
  payload = {
    'items' => items,
    'total' => total,
    'limit' => limit,
    'offset' => offset
  }
  emit_success(payload, command:)
end

#emit_success(data, command: nil) ⇒ Object



45
46
47
48
49
50
51
52
# File 'lib/mixin_bot/cli/output.rb', line 45

def emit_success(data, command: nil)
  command_name = command || current_command_name
  if structured_output?
    write_stdout(encode_output(envelope('ok', command_name, data)))
  else
    write_pretty(data)
  end
end

#log(obj) ⇒ Object



108
109
110
# File 'lib/mixin_bot/cli/output.rb', line 108

def log(obj)
  emit_success(obj)
end

#output_formatObject



19
20
21
22
23
24
25
26
27
# File 'lib/mixin_bot/cli/output.rb', line 19

def output_format
  opts = cli_options
  explicit = opts[:output].to_s.downcase if opts[:output].present?
  return explicit if OUTPUT_FORMATS.include?(explicit)

  return 'json' if opts.key?(:pretty) && opts[:pretty] == false

  $stdout.tty? ? 'pretty' : 'json'
end

#paginate_items(items, limit:, offset:) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/mixin_bot/cli/output.rb', line 112

def paginate_items(items, limit:, offset:)
  limit = limit.to_i
  offset = offset.to_i
  limit = 100 if limit <= 0
  offset = 0 if offset.negative?

  total = items.size
  slice = items.drop(offset).first(limit)
  [slice, total, limit, offset]
end


94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/mixin_bot/cli/output.rb', line 94

def print_result(obj, data_only: false, command: nil)
  out =
    case obj
    when MixinBot::Models::ApiEnvelope
      data_only ? (obj['data'] || obj.to_h) : obj.to_h
    when Hash
      data_only ? (obj['data'] || obj) : obj
    else
      obj
    end

  emit_success(out, command:)
end

#select_fields(items, fields) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/mixin_bot/cli/output.rb', line 123

def select_fields(items, fields)
  return items if fields.blank?

  keys = fields.split(',').map(&:strip).reject(&:empty?)
  return items if keys.empty?

  items.map do |item|
    item.slice(*keys)
  end
end

#structured_output?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/mixin_bot/cli/output.rb', line 29

def structured_output?
  %w[json yaml].include?(output_format)
end

#with_command_name(name) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/mixin_bot/cli/output.rb', line 37

def with_command_name(name)
  previous = @current_command_name
  @current_command_name = name
  yield
ensure
  @current_command_name = previous
end