output_mode 1.2.2 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec56475261c0c87de303f06bb9d5c046c71b39a5cdecaaab8c2a6ba0fc9d1602
4
- data.tar.gz: 6912cc2ae659c9c1ea89c0d2fe38b03537f3de9f54d6b981fc958c4519f78bcd
3
+ metadata.gz: a4518dd66cf4b3d2a7ea9ea255c3a5835a6075eea24f62620b39f62b405a625b
4
+ data.tar.gz: bdcd16455d48e576ce18ea2793613b99bef8424ce9c504aff210f1f7a62e7f2e
5
5
  SHA512:
6
- metadata.gz: 0e439ef9c8b4a68bc567d1d7b01d38b0582a85de7e08355c301a6012ef0c95c6c42af1f2c4ee4088cd2edd64566956d9b8e4a3ad94b48eded57f3cfafa0b987d
7
- data.tar.gz: 321eed2286b0e87ef5f45b2336a8a4c95e477258a00744087bafd85fbd3dfad1a3ee2f8b220cddcaf0a9e6193301de1e708e1890aaf10a5497c08ea4f44e150e
6
+ metadata.gz: d622a5d10d8e93d2787fa7fa7916e43d984af483181c52158ab747d3556196d97fa2425da9d99538bb1d4b193d14c75d1a77763a3ba2de336312666af8350409
7
+ data.tar.gz: 56f15aa86585eb8c6c7ba598d8780b597649305453162574e14b0116257a62ad2689e68daadb29328eb4c58f51a15e7737cf62650ea7f326bcf18c04c1bf2687
data/Gemfile CHANGED
@@ -28,3 +28,10 @@ source "https://rubygems.org"
28
28
 
29
29
  # Specify your gem's dependencies in output_mode.gemspec
30
30
  gemspec
31
+
32
+ # NOTE: Checks out the openflight version of tty-table with the 'rotate' flag
33
+ # fix. This should eventually become part of the mainline version of TTY::Table
34
+ #
35
+ # This is intentionally not part of the gemspec so older versions of TTY::Table
36
+ # can still be used
37
+ gem 'tty-table', github: 'openflighthpc/tty-table', branch: '9b326fcbe04968463da58c000fbb1dd5ce178243'
data/bin/demo CHANGED
@@ -27,6 +27,7 @@
27
27
 
28
28
  require "bundler/setup"
29
29
  require "output_mode"
30
+ require 'erb'
30
31
 
31
32
  module DemoIndex
32
33
  extend OutputMode::TLDR::Index
@@ -35,9 +36,22 @@ module DemoIndex
35
36
  register_callable(header: 'Standard', header_color: [:strikethrough] ) { 'always visible' }
36
37
  register_callable(header: 'Verbose', verbose: true) { 'verbose visible' }
37
38
  register_callable(header: 'Simplified', verbose: false) { 'simplified visible' }
39
+ register_callable(header: 'Interactive', interactive: true) { 'interactive visible' }
40
+ register_callable(header: 'Non Interactive', interactive: false) { 'non-interactive visible' }
38
41
  register_callable(header: 'Yes/True') { true }
39
42
  register_callable(header: 'No/False', row_color: [:clear]) { false }
40
43
  register_callable(header: 'Missing') { nil }
44
+ register_callable(header: 'Inline') do |interactive:, verbose:|
45
+ if interactive && verbose
46
+ 'interactive-verbose'
47
+ elsif interactive
48
+ 'interactive-simplified'
49
+ elsif verbose
50
+ 'non-interactive-verbose'
51
+ else
52
+ 'non-interactive-simplified'
53
+ end
54
+ end
41
55
  end
42
56
 
43
57
  module DemoShow
@@ -47,13 +61,29 @@ module DemoShow
47
61
  register_callable(header: 'Standard') { 'always visible' }
48
62
  register_callable(header: 'Verbose', verbose: true) { 'verbose visible' }
49
63
  register_callable(header: 'Simplified', verbose: false) { 'simplified visible' }
50
- register_callable(header: 'Yes/True') { true }
51
- register_callable(header: 'No/False') { false }
64
+ register_callable(header: 'Interactive', interactive: true) { 'interactive visible' }
65
+ register_callable(header: 'Non Interactive', interactive: false) { 'non-interactive visible' }
66
+ register_callable(header: 'Yes/True', section: :boolean) { true }
67
+ register_callable(header: 'No/False', section: :boolean) { false }
52
68
  register_callable(header: 'Missing') { nil }
69
+ register_callable(header: 'Tab') { "tab1\ttab2" }
70
+ register_callable(header: 'New line') { "line1\nline2" }
53
71
  end
54
72
 
55
73
  data = [1, 2, 3]
56
74
 
75
+ other_template = ERB.new(<<~TEMPLATE, nil, '-')
76
+ # Non boolean values
77
+ <% each(:other) do |value, field:, padding:, **_| -%>
78
+ <%= padding -%><%= pastel.blue.bold field -%><%= pastel.bold ':' -%> <%= pastel.green value %>
79
+ <% end -%>
80
+
81
+ # Boolean Values
82
+ <% each(:boolean) do |value, field:, padding:, **_| -%>
83
+ <%= padding -%><%= pastel.blue.bold field -%><%= pastel.bold ':' -%> <%= pastel.green value %>
84
+ <% end -%>
85
+ TEMPLATE
86
+
57
87
  puts <<~EOF
58
88
  #==============================================================================
59
89
  #==============================================================================
@@ -61,6 +91,12 @@ puts <<~EOF
61
91
  #==============================================================================
62
92
  #==============================================================================
63
93
 
94
+ #==============================================================================
95
+ # Default Demo Index
96
+ # Simplified in interactive shells, verbose in non-interactive
97
+ #==============================================================================
98
+ #{DemoIndex.build_output.render(*data)}
99
+
64
100
  #==============================================================================
65
101
  # Demo Verbose Index
66
102
  #==============================================================================
@@ -68,7 +104,6 @@ puts <<~EOF
68
104
 
69
105
  #==============================================================================
70
106
  # Demo "Simplified" Index
71
- # NOTE: Disabled for non-interactive shell, shows the verbose output instead
72
107
  #==============================================================================
73
108
  #{DemoIndex.build_output(verbose: false).render(*data)}
74
109
 
@@ -95,6 +130,12 @@ puts <<~EOF
95
130
  #==============================================================================
96
131
  #==============================================================================
97
132
 
133
+ #==============================================================================
134
+ # Default Settings
135
+ # Simplified in interactive shells, verbose in non-interactive
136
+ #==============================================================================
137
+ #{DemoShow.build_output.render(*data)}
138
+
98
139
  #==============================================================================
99
140
  # Demo Verbose Show
100
141
  #==============================================================================
@@ -102,7 +143,6 @@ puts <<~EOF
102
143
 
103
144
  #==============================================================================
104
145
  # Demo "Simplified" Show
105
- # NOTE: Disabled for non-interactive shell, shows the verbose output instead
106
146
  #==============================================================================
107
147
  #{DemoShow.build_output(verbose: false).render(*data)}
108
148
 
@@ -122,4 +162,11 @@ puts <<~EOF
122
162
  # Demo ASCII Index
123
163
  #==============================================================================
124
164
  #{DemoShow.build_output(ascii: true).render(*data)}
165
+
166
+ #==============================================================================
167
+ # Group the boolean value separately
168
+ # NOTE: This only occurs in interactive mode
169
+ # Non-Interactive sessions have a fix order
170
+ #==============================================================================
171
+ #{DemoShow.build_output(template: other_template).render(*data)}
125
172
  EOF
@@ -36,7 +36,9 @@ module OutputMode
36
36
  # @param config Directly provided to {OutputMode::Callable#initialize}
37
37
  # @yield Directly provided to {OutputMode::Callable#initialize}
38
38
  def register_callable(**config, &b)
39
- output_callables << Callable.new(**config, &b)
39
+ Callable.new(**config, &b).tap do |c|
40
+ output_callables << c
41
+ end
40
42
  end
41
43
 
42
44
  # Provides the base method signature
@@ -26,16 +26,17 @@
26
26
 
27
27
  module OutputMode
28
28
  # Internal array like object that will convert procs to Callable
29
- class Callables < Array
29
+ class Callables
30
+ include Enumerable
31
+
30
32
  # @api private
31
33
  def initialize(callables = nil)
34
+ @callables = []
32
35
  case callables
33
- when Array
34
- super().tap do |all|
35
- callables.each { |c| all << c }
36
- end
36
+ when Array, Callables
37
+ callables.each { |c| @callables << c }
37
38
  when nil
38
- super()
39
+ # NOOP
39
40
  else
40
41
  raise "Can not convert #{callables.class} into a #{self.class}"
41
42
  end
@@ -43,13 +44,61 @@ module OutputMode
43
44
 
44
45
  def <<(item)
45
46
  if item.is_a? Callable
46
- super
47
+ @callables << item
47
48
  elsif item.respond_to?(:call)
48
- super(Callable.new(&item))
49
+ @callables << Callable.new(&item)
49
50
  else
50
51
  raise Error, "#{item.class} is not callable"
51
52
  end
52
53
  end
54
+
55
+ def each(&block)
56
+ @callables.each(&block)
57
+ end
58
+
59
+ def pad_each(*ctx, **input_opts)
60
+ fields = self.map do |callables|
61
+ field = callables.config[:header]
62
+ if field.respond_to?(:call)
63
+ opts = if field.parameters.include?(:keyrest)
64
+ input_opts.dup
65
+ else
66
+ keys = field.parameters
67
+ .select { |type, _| [:key, :keyreq].include?(type) }
68
+ .map { |_, k| k }
69
+ input_opts.slice(*keys)
70
+ end
71
+ opts.empty? ? field.call(*ctx) : field.call(*ctx, **opts)
72
+ else
73
+ field.to_s
74
+ end
75
+ end
76
+
77
+ max_length = fields.map(&:length).max
78
+ pads = self.each_with_index.map do |callable, idx|
79
+ field = fields[idx]
80
+ length = max_length - field.length
81
+ [callable, { padding: ' ' * length, field: field }]
82
+ end
83
+
84
+ if block_given?
85
+ pads.each { |c, opts| yield(c, **opts) }
86
+ else
87
+ pads.each
88
+ end
89
+ end
90
+
91
+ def config_select(key, *values)
92
+ selected = self.select do |callable|
93
+ conf = callable.config[key]
94
+ if conf.is_a? Array
95
+ !(conf & values).empty?
96
+ else
97
+ values.include?(conf)
98
+ end
99
+ end
100
+ Callables.new(selected)
101
+ end
53
102
  end
54
103
 
55
104
  class Callable
@@ -141,6 +190,29 @@ module OutputMode
141
190
  def call(*a)
142
191
  callable.call(*a)
143
192
  end
193
+
194
+ def generator(output)
195
+ ->(*a) do
196
+ # Implicitly determine which parts of the context can be passed through
197
+ ctx = if callable.parameters.any? { |type, _| type == :keyrest }
198
+ output.context
199
+ else
200
+ keys = callable.parameters.select { |type, _| [:key, :keyreq].include?(type) }
201
+ .map { |_, k| k }
202
+ output.context.slice(*keys)
203
+ end
204
+ raw = call(*a, **ctx)
205
+ if raw == true
206
+ config[:yes] || output.yes
207
+ elsif raw == false
208
+ config[:no] || output.no
209
+ elsif [nil, ''].include?(raw)
210
+ config[:default] || output.default
211
+ else
212
+ raw
213
+ end
214
+ end
215
+ end
144
216
  end
145
217
  end
146
218
 
@@ -36,45 +36,41 @@ module OutputMode
36
36
  # @!attribute [r] config
37
37
  # @return [Hash] additional key-values to modify the render
38
38
  # @!attribute [r] default
39
- # @return either a static default or a column based array of defaults
39
+ # @return either a static default
40
40
  # @!attribute [r] yes
41
- # @return either a static yes value or a column based array of values
41
+ # @return either a static yes value
42
42
  # @!attribute [r] no
43
- # @return either a static no value or a column based array of values
44
- attr_reader :procs, :config, :yes, :no, :default
43
+ # @return either a static no value
44
+ # @!attribute [r] context
45
+ # @return a hash of keys to be provided to the callables
46
+ attr_reader :procs, :config, :yes, :no, :default, :context
45
47
 
46
48
  # Creates a new outputting instance from an array of procs
47
49
  #
48
50
  # @param *procs [Array<#call>] an array of procs (or callable objects)
49
51
  # @param default: [String] replaces _blanks_ with a static string
50
- # @param default: [Array] replace _blanks_ on a per column basis. The last value is repeated if the +procs+ are longer.
51
52
  # @param yes: [String] replaces +true+ with a static string
52
- # @param yes: [Array] replaces +true+ on a per column basis. The last value is repeated if the +procs+ are longer.
53
53
  # @param no: [String] replaces +false+ with a static string
54
- # @param no: [Array] replaces +false+ on a per column basis. The last value is repeated if the +procs+ are longer.
54
+ # @param context: [Hash] of keys to be provided to the callables
55
55
  # @param **config [Hash] a hash of additional keys to be stored
56
- def initialize(*procs, default: nil, yes: 'true', no: 'false', **config)
57
- @procs = procs
56
+ def initialize(*procs, default: nil, yes: 'true', no: 'false', context: {}, **config)
57
+ @procs = Callables.new(procs)
58
58
  @config = config
59
59
  @yes = yes
60
60
  @no = no
61
61
  @default = default
62
+ @context = context
63
+ end
64
+
65
+ def callables
66
+ procs
62
67
  end
63
68
 
64
69
  # Returns the results of the +procs+ for a particular +object+. It will apply the
65
70
  # +default+, +yes+, and +no+ values.
66
71
  def generate(object)
67
- procs.each_with_index.map do |p, idx|
68
- raw = p.call(object)
69
- if raw == true
70
- index_selector(:yes, idx)
71
- elsif raw == false
72
- index_selector(:no, idx)
73
- elsif !default.nil? && (raw.nil? || raw == '')
74
- index_selector(:default, idx)
75
- else
76
- raw
77
- end
72
+ procs.map do |callable|
73
+ callable.generator(self).call(object)
78
74
  end
79
75
  end
80
76
 
@@ -91,37 +87,5 @@ module OutputMode
91
87
  def render(*data)
92
88
  raise NotImplementedError
93
89
  end
94
-
95
- # A helper method for selecting elements from a source array or return
96
- # a static value.
97
- #
98
- # @param [Symbol] method The source method on the +output+
99
- # @param [Integer] index The index to lookup
100
- #
101
- # @overload index_selector(array_method, valid_index)
102
- # @param array_method A method that returns an array
103
- # @param valid_index An index that is less than the array's length
104
- # @return the value at the index
105
- #
106
- # @overload index_selector(array_method, out_of_bounds)
107
- # @param array_method A method that returns an array
108
- # @param out_of_bounds An index greater than the maximum array length
109
- # @return the last element of the array
110
- #
111
- # @overload index_selector(non_array_method, _)
112
- # @param non_array_method A method that does not return an array
113
- # @param _ The index is ignored
114
- # @return the result of the non_array_method
115
- def index_selector(method, index)
116
- source = public_send(method)
117
- is_array = source.is_a? Array
118
- if is_array && source.length > index
119
- source[index]
120
- elsif is_array
121
- source.last
122
- else
123
- source
124
- end
125
- end
126
90
  end
127
91
  end
@@ -40,7 +40,12 @@ module OutputMode
40
40
  def render(*data)
41
41
  io = StringIO.new
42
42
  csv = CSV.new(io, **config)
43
- data.each { |d| csv << generate(d) }
43
+ data.each do |datum|
44
+ csv << generate(datum).map do |value|
45
+ next nil if value.nil?
46
+ value.to_s.dump[1...-1]
47
+ end
48
+ end
44
49
  io.tap(&:rewind).read
45
50
  end
46
51
  end
@@ -31,8 +31,6 @@ module OutputMode
31
31
  class Tabulated < Output
32
32
  # @!attribute [r] renderer
33
33
  # @return [Symbol] the renderer type, see: https://github.com/piotrmurach/tty-table#32-renderer
34
- # @!attribute [r] header
35
- # @return [Array] An optional header row for the table
36
34
  # @!attribute [r] block
37
35
  # @return [#call] an optional block of code that configures the renderer
38
36
  # @!attribute [r] colorize
@@ -41,28 +39,25 @@ module OutputMode
41
39
  # @return An optional header color or array of colors
42
40
  # @!attribute [r] row_color
43
41
  # @return An optional data color or array of colors
44
- attr_reader :renderer, :header, :default, :block, :yes, :no,
42
+ attr_reader :renderer, :default, :block, :yes, :no,
45
43
  :header_color, :row_color, :colorize
46
44
 
47
45
  # @return [Hash] additional options to +TTY::Table+ renderer
48
46
  # @see https://github.com/piotrmurach/tty-table#33-options
49
47
  def config; super; end
50
48
 
51
- # @overload initialize(*procs, renderer: nil, header: nil, **config)
49
+ # @overload initialize(*procs, renderer: nil, **config)
52
50
  # @param [Array] *procs see {OutputMode::Outputs::Base#initialize}
53
51
  # @param [Symbol] :renderer override the default renderer
54
- # @param [Array<String>] :header the header row of the table
55
52
  # @param [Hash] **config additional options to the renderer
56
53
  # @yieldparam tty_table_renderer [TTY::Table::Renderer::Base] optional access the underlining TTY::Table renderer
57
54
  def initialize(*procs,
58
55
  renderer: :unicode,
59
56
  colorize: false,
60
- header: nil,
61
57
  header_color: nil,
62
58
  row_color: nil,
63
59
  **config,
64
60
  &block)
65
- @header = header
66
61
  @renderer = renderer
67
62
  @block = block
68
63
  @header_color = header_color
@@ -76,7 +71,7 @@ module OutputMode
76
71
  # @see https://github.com/piotrmurach/tty-table
77
72
  def render(*data)
78
73
  table = TTY::Table.new header: processed_header
79
- data.each { |d| table << process_row(generate(d)) }
74
+ data.each { |d| table << process_row(d) }
80
75
  table.render(renderer, **config, &block) || ''
81
76
  end
82
77
 
@@ -84,23 +79,25 @@ module OutputMode
84
79
 
85
80
  # Colorizes the header when requested
86
81
  def processed_header
87
- header&.each_with_index&.map do |h, idx|
88
- color = index_selector(:header_color, idx)
82
+ callables.map do |callable|
83
+ header = callable.config.fetch(:header, '')
84
+ color = callable.config.fetch(:header_color) || header_color
89
85
  case color
90
86
  when nil
91
- h.to_s
87
+ header.to_s
92
88
  when Array
93
- pastel.decorate(h.to_s, *color)
89
+ pastel.decorate(header.to_s, *color)
94
90
  else
95
- pastel.decorate(h.to_s, color)
91
+ pastel.decorate(header.to_s, color)
96
92
  end
97
93
  end
98
94
  end
99
95
 
100
96
  # Colorizes the row when requested
101
- def process_row(data)
102
- data.each_with_index.map do |d, idx|
103
- color = index_selector(:row_color, idx)
97
+ def process_row(model)
98
+ callables.map do |callable|
99
+ d = callable.generator(self).call(model)
100
+ color = callable.config[:row_color] || row_color
104
101
  case color
105
102
  when NilClass
106
103
  d.to_s
@@ -38,13 +38,24 @@ module OutputMode
38
38
  # @yieldparam field: An optional field header for the value
39
39
  # @yieldparam padding: A padding string which will right align the +field+
40
40
  # @yieldparam **config TBA
41
- def each
42
- max = output.max_field_length
43
- output.generate(model).each_with_index do |value, idx|
44
- field = output.index_selector(:fields, idx)
45
- padding = ' ' * (max - field.to_s.length)
46
- yield(value, field: field, padding: padding)
41
+ def each(section = nil, &block)
42
+ # Select the callable objects
43
+ callables = if section == nil
44
+ output.callables
45
+ elsif section == :default
46
+ output.callables.config_select(:section, :default, nil)
47
+ else
48
+ output.callables.config_select(:section, section)
49
+ end
50
+
51
+ # Yield each selected attribute
52
+ objs = callables.pad_each(model, **output.context).map do |callable, padding:, field:|
53
+ value = callable.generator(output).call(model)
54
+ [value, { field: field, padding: padding }]
47
55
  end
56
+
57
+ # Runs the provided block
58
+ objs.each(&block)
48
59
  end
49
60
 
50
61
  # Renders an ERB object within the entry's context. This provides access to the
@@ -61,6 +72,12 @@ module OutputMode
61
72
  def pastel
62
73
  @pastel ||= Pastel.new(enabled: colorize)
63
74
  end
75
+
76
+ private
77
+
78
+ def generated
79
+ @generated ||= output.generate(model)
80
+ end
64
81
  end
65
82
 
66
83
  # @!attribute [r] erb
@@ -68,7 +85,8 @@ module OutputMode
68
85
  # @!attribute [r] separator
69
86
  # @!attribute [r] fields
70
87
  # @!attribute [r] colorize
71
- attr_reader :erb, :fields, :separator, :colorize
88
+ # @!attribute [r] sections
89
+ attr_reader :erb, :fields, :separator, :colorize, :sections
72
90
 
73
91
  # Create a new +output+ which will render using +ERB+. The provided +template+ should
74
92
  # only render the +output+ for a single +entry+ (aka model, record, data object, etc).
@@ -84,23 +102,32 @@ module OutputMode
84
102
  #
85
103
  # @overload initialize(*procs, template: nil, fields: nil, seperator: "\n", yes: 'true', no: 'false', **config)
86
104
  # @param [Array] *procs see {OutputMode::Output#initialize}
87
- # @param [String] template: A string to be converted into +ERB+
88
105
  # @param [ERB] template: The +template+ object used by the renderer
89
106
  # @param [Array] fields: An optional array of field headers that map to the procs, repeating the last value if required
90
107
  # @param fields: A static value to use as all field headers
91
108
  # @param separator: The character(s) used to join the "entries" together
92
109
  # @param colorize: Flags if the caller wants the colorized version, this maybe ignored by +template+
110
+ # @param sections: An optional array that groups the procs into sections. This is ignored by default
93
111
  # @param [Hash] **config see {OutputMode::Output#initialize}
94
112
  def initialize(*procs,
95
113
  template: nil,
96
114
  fields: nil,
97
115
  separator: "\n",
98
116
  colorize: false,
117
+ sections: nil,
99
118
  **config)
100
- @erb = DEFAULT_ERB
119
+ @erb = case template
120
+ when String
121
+ ERB.new(template, nil, '-')
122
+ when ERB
123
+ template
124
+ else
125
+ DEFAULT_ERB
126
+ end
101
127
  @fields = fields
102
128
  @separator = separator
103
129
  @colorize = colorize
130
+ @sections = sections
104
131
  super(*procs, **config)
105
132
  end
106
133
 
@@ -35,12 +35,18 @@ module OutputMode
35
35
  # @overload register_callable(header:, verbose: true)
36
36
  # @param header: The column's header field when displaying to humans
37
37
  # @param verbose: Whether the column will be shown in the verbose output
38
+ # @param interactive: Whether the field will be show in the interactive output
38
39
  # @param header_color: Override the default color for the header
39
40
  # @param row_color: Override the default color for the row
41
+ # @param modes: Additional modes flags for the callable
40
42
  # @yieldparam model The subject the column is describing, some sort of data model
41
- def register_callable(header:, verbose: nil, header_color: nil, row_color: nil, &b)
42
- super(modes: { verbose: verbose }, header: header,
43
- header_color: header_color, row_color: row_color, &b)
43
+ def register_callable(modes: {}, header:, verbose: nil, interactive: nil, header_color: nil, row_color: nil, &b)
44
+ modes = modes.map { |m| [m, true] }.to_h if modes.is_a? Array
45
+ super(modes: modes.merge(verbose: verbose, interactive: interactive),
46
+ header: header,
47
+ header_color: header_color,
48
+ row_color: row_color,
49
+ &b)
44
50
  end
45
51
  alias_method :register_column, :register_callable
46
52
 
@@ -65,8 +71,16 @@ module OutputMode
65
71
  #
66
72
  # An interative/ non-interactive output can be forced by setting the
67
73
  # +interactive+ flag to +true+/+false+ respectively
68
- def build_output(verbose: false, ascii: false, interactive: nil, header_color: [:blue, :bold], row_color: :green)
69
- callables = if verbose || !$stdout.tty?
74
+ def build_output(verbose: nil, ascii: nil, interactive: nil, header_color: [:blue, :bold], row_color: :green, context: {})
75
+ # Set the interactive and verbose flags if not provided
76
+ interactive = $stdout.tty? if interactive.nil?
77
+ verbose = !interactive if verbose.nil?
78
+ ascii = !interactive if ascii.nil?
79
+
80
+ # Update the rendering context with the verbosity/interactive settings
81
+ context = context.merge(interactive: interactive, verbose: verbose, ascii: ascii)
82
+
83
+ callables = if verbose
70
84
  # Filter out columns that are explicitly not verbose
71
85
  output_callables.select { |o| o.verbose?(true) }
72
86
  else
@@ -74,27 +88,37 @@ module OutputMode
74
88
  output_callables.reject(&:verbose?)
75
89
  end
76
90
 
77
- if interactive || (interactive.nil? && $stdout.tty?)
91
+ callables = if interactive
92
+ # Filter out columns that are explicitly not interactive
93
+ callables.select { |o| o.interactive?(true) }
94
+ else
95
+ # Filter out columns that are explicitly interactive
96
+ callables.reject { |o| o.interactive? }
97
+ end
98
+
99
+ if interactive
78
100
  # Creates the human readable output
79
101
  opts = if ascii
80
102
  { yes: 'yes', no: 'no', renderer: :ascii }
81
103
  else
82
104
  {
83
105
  yes: '✓', no: '✕', renderer: :unicode, colorize: TTY::Color.color?,
84
- header_color: callables.map { |c| c.config[:header_color] || header_color },
85
- row_color: callables.map { |c| c.config[:row_color] || row_color }
106
+ header_color: header_color,
107
+ row_color: row_color
86
108
  }
87
109
  end
88
110
 
89
111
  Outputs::Tabulated.new(*callables,
90
- header: callables.map { |c| c.config.fetch(:header, 'missing') },
112
+ rotate: false,
91
113
  padding: [0,1],
92
114
  default: '(none)',
115
+ context: context,
93
116
  **opts
94
117
  )
95
118
  else
96
119
  # Creates the machine readable output
97
- Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: '')
120
+ Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: nil,
121
+ context: context)
98
122
  end
99
123
  end
100
124
  end
@@ -35,9 +35,16 @@ module OutputMode
35
35
  # @overload register_callable(header:, verbose: true)
36
36
  # @param header: The human readable key to the field, uses the term 'header' for consistency
37
37
  # @param verbose: Whether the field will be shown in the verbose output
38
+ # @param interactive: Whether the field will be show in the interactive output
39
+ # @param section: Define the grouping a callable belongs to. Ignored by default
40
+ # @param modes: Additional modes flags for the callable
38
41
  # @yieldparam model The subject the column is describing, some sort of data model
39
- def register_callable(header:, verbose: nil, &b)
40
- super(modes: { verbose: verbose }, header: header, &b)
42
+ def register_callable(modes: {}, header:, verbose: nil, interactive: nil, section: :default, &b)
43
+ modes = modes.map { |m| [m, true] }.to_h if modes.is_a? Array
44
+ super(modes: modes.merge(verbose: verbose, interactive: interactive),
45
+ header: header,
46
+ section: section,
47
+ &b)
41
48
  end
42
49
  alias_method :register_attribute, :register_callable
43
50
 
@@ -60,10 +67,20 @@ module OutputMode
60
67
  # for consumption by machines. This output ignores the provided +verbose+
61
68
  # flag as it is always verbose.
62
69
  #
70
+ # The +template+ overrides the default erb template for the output
71
+ #
63
72
  # An interative/ non-interactive output can be forced by setting the
64
73
  # +interactive+ flag to +true+/+false+ respectively
65
- def build_output(verbose: false, ascii: false, interactive: nil)
66
- callables = if verbose || !$stdout.tty?
74
+ def build_output(verbose: nil, ascii: nil, interactive: nil, template: nil, context: {})
75
+ # Set the interactive and verbose flags if not provided
76
+ interactive = $stdout.tty? if interactive.nil?
77
+ verbose = !interactive if verbose.nil?
78
+ ascii = !interactive if ascii.nil?
79
+
80
+ # Update the rendering context with the verbosity/interactive settings
81
+ context = context.merge(interactive: interactive, verbose: verbose, ascii: ascii)
82
+
83
+ callables = if verbose
67
84
  # Filter out columns that are explicitly not verbose
68
85
  output_callables.select { |o| o.verbose?(true) }
69
86
  else
@@ -71,21 +88,35 @@ module OutputMode
71
88
  output_callables.reject(&:verbose?)
72
89
  end
73
90
 
74
- if interactive || (interactive.nil? && $stdout.tty?)
91
+ callables = if interactive
92
+ # Filter out columns that are explicitly not interactive
93
+ callables.select { |o| o.interactive?(true) }
94
+ else
95
+ # Filter out columns that are explicitly interactive
96
+ callables.reject { |o| o.interactive? }
97
+ end
98
+
99
+ if interactive
75
100
  # Creates the human readable output
76
- opts = if ascii
77
- { yes: 'yes', no: 'no', colorize: false }
78
- else
79
- { yes: '✓', no: '✕', colorize: TTY::Color.color? }
101
+ opts = if ascii
102
+ { yes: 'yes', no: 'no', colorize: false }
103
+ else
104
+ { yes: '✓', no: '✕', colorize: TTY::Color.color? }
80
105
  end
81
106
 
107
+ sections = callables.map { |o| o.config[:section] }
108
+
82
109
  Outputs::Templated.new(*callables,
83
110
  fields: callables.map { |c| c.config.fetch(:header, 'missing') },
84
111
  default: '(none)',
112
+ sections: sections,
113
+ template: template,
114
+ context: context,
85
115
  **opts)
86
116
  else
87
117
  # Creates the machine readable output
88
- Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: '')
118
+ Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: nil,
119
+ context: context)
89
120
  end
90
121
  end
91
122
  end
@@ -25,5 +25,5 @@
25
25
  #==============================================================================
26
26
 
27
27
  module OutputMode
28
- VERSION = "1.2.2"
28
+ VERSION = "1.5.2"
29
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: output_mode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - William McCumsite
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-04 00:00:00.000000000 Z
11
+ date: 2021-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-table