output_mode 1.1.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5a9cfc28a765bc0e31db2d2247f346f127cf99c4f4e630048b4882a539c1a03
4
- data.tar.gz: 168a9b20db06828042673f7b4826f77367a10d283d2212a20d88eca3232fcde4
3
+ metadata.gz: f4492bb6d42dfd55f9f83ba7a3fd7fa9b21a2712760272cbd1d3500dafc11dd4
4
+ data.tar.gz: 71068e9161e4219ab85508a6692046f2de51d19a83c685b40fa9212ac0d40c46
5
5
  SHA512:
6
- metadata.gz: bbefcfef7c8d46b95113b0877e84de60f852b8c3bac4d74575414215235796195c4f8447044ee92db11029e9edbfc269f092776c69e714fc8d91fc43319230bd
7
- data.tar.gz: 3ff9cc52f21ab132ae2a673100354ff7e84eee824388315abf5563aeb181b35f82920d778e7e89bfbaa1e832bd31d377ce2865e1f5c73d5e88f7d6bafcdbfe6f
6
+ metadata.gz: 05d68669d5abbb2ac911531c8709d4d44898fcaae4fced442d5bf8ffc2d935d5c2ef8319bc2b28cb5e33fb7b033b236c57992d449b1de4c69f667b3248c8d08f
7
+ data.tar.gz: c6aae6fc8496bb491b4213173c4fc7bd57499efd9f2ec1e4367f3ec46b849c6be41453f17065c44a0b687aa40c1b6d1c677fe9578ad3dea11f8a92e31722f78a
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/README.md CHANGED
@@ -63,5 +63,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Willia
63
63
 
64
64
  ## Copyright and License
65
65
 
66
- See [LICENSE](LICENSE.txt) for dual licensing details.
66
+ See [LICENSE](LICENSE.txt) for licensing details.
67
67
 
data/bin/demo CHANGED
@@ -1,22 +1,57 @@
1
1
  #!/usr/bin/env ruby
2
2
  #==============================================================================
3
- # Refer to LICENSE.txt for licensing terms
3
+ # Copyright 2020 William McCumstie
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
+ # POSSIBILITY OF SUCH DAMAGE.
4
26
  #==============================================================================
5
27
 
6
-
7
28
  require "bundler/setup"
8
29
  require "output_mode"
30
+ require 'erb'
9
31
 
10
32
  module DemoIndex
11
33
  extend OutputMode::TLDR::Index
12
34
 
13
- register_callable(header: 'Integer') { |i| i }
14
- register_callable(header: 'Standard') { 'always visible' }
35
+ register_callable(header: 'Integer', row_color: [:yellow, :bold]) { |i| i }
36
+ register_callable(header: 'Standard', header_color: [:strikethrough] ) { 'always visible' }
15
37
  register_callable(header: 'Verbose', verbose: true) { 'verbose visible' }
16
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' }
17
41
  register_callable(header: 'Yes/True') { true }
18
- register_callable(header: 'No/False') { false }
42
+ register_callable(header: 'No/False', row_color: [:clear]) { false }
19
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
20
55
  end
21
56
 
22
57
  module DemoShow
@@ -26,14 +61,42 @@ module DemoShow
26
61
  register_callable(header: 'Standard') { 'always visible' }
27
62
  register_callable(header: 'Verbose', verbose: true) { 'verbose visible' }
28
63
  register_callable(header: 'Simplified', verbose: false) { 'simplified visible' }
29
- register_callable(header: 'Yes/True') { true }
30
- 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 }
31
68
  register_callable(header: 'Missing') { nil }
69
+ register_callable(header: 'Tab') { "tab1\ttab2" }
70
+ register_callable(header: 'New line') { "line1\nline2" }
32
71
  end
33
72
 
34
73
  data = [1, 2, 3]
35
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
+
36
87
  puts <<~EOF
88
+ #==============================================================================
89
+ #==============================================================================
90
+ # INDEX OUTPUTS
91
+ #==============================================================================
92
+ #==============================================================================
93
+
94
+ #==============================================================================
95
+ # Default Demo Index
96
+ # Simplified in interactive shells, verbose in non-interactive
97
+ #==============================================================================
98
+ #{DemoIndex.build_output.render(*data)}
99
+
37
100
  #==============================================================================
38
101
  # Demo Verbose Index
39
102
  #==============================================================================
@@ -41,10 +104,38 @@ puts <<~EOF
41
104
 
42
105
  #==============================================================================
43
106
  # Demo "Simplified" Index
44
- # NOTE: Disabled for non-interactive shell, shows the verbose output instead
45
107
  #==============================================================================
46
108
  #{DemoIndex.build_output(verbose: false).render(*data)}
47
109
 
110
+ #==============================================================================
111
+ # Force Interactive
112
+ # Always print as if the shell is interactive
113
+ #==============================================================================
114
+ #{DemoIndex.build_output(interactive: true).render(*data)}
115
+
116
+ #==============================================================================
117
+ # Force Non-Interactive
118
+ # Always print as if the shell is non-interactive
119
+ #==============================================================================
120
+ #{DemoIndex.build_output(interactive: false).render(*data)}
121
+
122
+ #==============================================================================
123
+ # Demo ASCII Index
124
+ #==============================================================================
125
+ #{DemoIndex.build_output(ascii: true).render(*data)}
126
+
127
+ #==============================================================================
128
+ #==============================================================================
129
+ # SHOW OUTPUTS
130
+ #==============================================================================
131
+ #==============================================================================
132
+
133
+ #==============================================================================
134
+ # Default Settings
135
+ # Simplified in interactive shells, verbose in non-interactive
136
+ #==============================================================================
137
+ #{DemoShow.build_output.render(*data)}
138
+
48
139
  #==============================================================================
49
140
  # Demo Verbose Show
50
141
  #==============================================================================
@@ -52,9 +143,30 @@ puts <<~EOF
52
143
 
53
144
  #==============================================================================
54
145
  # Demo "Simplified" Show
55
- # NOTE: Disabled for non-interactive shell, shows the verbose output instead
56
146
  #==============================================================================
57
147
  #{DemoShow.build_output(verbose: false).render(*data)}
58
- EOF
59
148
 
149
+ #==============================================================================
150
+ # Force Interactive
151
+ # Always print as if the shell is interactive
152
+ #==============================================================================
153
+ #{DemoShow.build_output(interactive: true).render(*data)}
154
+
155
+ #==============================================================================
156
+ # Force Non-Interactive
157
+ # Always print as if the shell is non-interactive
158
+ #==============================================================================
159
+ #{DemoShow.build_output(interactive: false).render(*data)}
160
+
161
+ #==============================================================================
162
+ # Demo ASCII Index
163
+ #==============================================================================
164
+ #{DemoShow.build_output(ascii: true).render(*data)}
60
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)}
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,55 @@ 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(key = :header, *ctx)
60
+ fields = self.map do |callables|
61
+ field = callables.config[key]
62
+ if field.respond_to?(:call) && ctx.empty?
63
+ raise Error, 'Can not pad dynamic fields without a context'
64
+ elsif field.respond_to?(:call)
65
+ field.call(*ctx).to_s
66
+ else
67
+ field.to_s
68
+ end
69
+ end
70
+
71
+ max_length = fields.map(&:length).max
72
+ pads = self.each_with_index.map do |callable, idx|
73
+ field = fields[idx]
74
+ length = max_length - field.length
75
+ [callable, { padding: ' ' * length, field: field }]
76
+ end
77
+
78
+ if block_given?
79
+ pads.each { |*args| yield(*args) }
80
+ else
81
+ pads.each
82
+ end
83
+ end
84
+
85
+ def config_select(key, *values)
86
+ selected = self.select do |callable|
87
+ conf = callable.config[key]
88
+ if conf.is_a? Array
89
+ !(conf & values).empty?
90
+ else
91
+ values.include?(conf)
92
+ end
93
+ end
94
+ Callables.new(selected)
95
+ end
53
96
  end
54
97
 
55
98
  class Callable
@@ -141,6 +184,29 @@ module OutputMode
141
184
  def call(*a)
142
185
  callable.call(*a)
143
186
  end
187
+
188
+ def generator(output)
189
+ ->(*a) do
190
+ # Implicitly determine which parts of the context can be passed through
191
+ ctx = if callable.parameters.any? { |type, _| type == :keyrest }
192
+ output.context
193
+ else
194
+ keys = callable.parameters.select { |type, _| [:key, :keyreq].include?(type) }
195
+ .map { |_, k| k }
196
+ output.context.slice(*keys)
197
+ end
198
+ raw = call(*a, **ctx)
199
+ if raw == true
200
+ config[:yes] || output.yes
201
+ elsif raw == false
202
+ config[:no] || output.no
203
+ elsif [nil, ''].include?(raw)
204
+ config[:default] || output.default
205
+ else
206
+ raw
207
+ end
208
+ end
209
+ end
144
210
  end
145
211
  end
146
212
 
@@ -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
@@ -29,33 +29,40 @@ require 'tty-table'
29
29
  module OutputMode
30
30
  module Outputs
31
31
  class Tabulated < Output
32
- attr_reader :renderer, :header, :default, :block, :yes, :no
33
-
34
32
  # @!attribute [r] renderer
35
33
  # @return [Symbol] the renderer type, see: https://github.com/piotrmurach/tty-table#32-renderer
36
- # @!attribute [r] header
37
- # @return [Array] An optional header row for the table
38
34
  # @!attribute [r] block
39
35
  # @return [#call] an optional block of code that configures the renderer
36
+ # @!attribute [r] colorize
37
+ # @return [Boolean] enable or disabled the colorization
38
+ # @!attribute [r] header_color
39
+ # @return An optional header color or array of colors
40
+ # @!attribute [r] row_color
41
+ # @return An optional data color or array of colors
42
+ attr_reader :renderer, :default, :block, :yes, :no,
43
+ :header_color, :row_color, :colorize
40
44
 
41
45
  # @return [Hash] additional options to +TTY::Table+ renderer
42
46
  # @see https://github.com/piotrmurach/tty-table#33-options
43
47
  def config; super; end
44
48
 
45
- # @overload initialize(*procs, renderer: nil, header: nil, **config)
49
+ # @overload initialize(*procs, renderer: nil, **config)
46
50
  # @param [Array] *procs see {OutputMode::Outputs::Base#initialize}
47
51
  # @param [Symbol] :renderer override the default renderer
48
- # @param [Array<String>] :header the header row of the table
49
52
  # @param [Hash] **config additional options to the renderer
50
53
  # @yieldparam tty_table_renderer [TTY::Table::Renderer::Base] optional access the underlining TTY::Table renderer
51
54
  def initialize(*procs,
52
55
  renderer: :unicode,
53
- header: nil,
56
+ colorize: false,
57
+ header_color: nil,
58
+ row_color: nil,
54
59
  **config,
55
60
  &block)
56
- @header = header
57
61
  @renderer = renderer
58
62
  @block = block
63
+ @header_color = header_color
64
+ @row_color = row_color
65
+ @colorize = colorize
59
66
  super(*procs, **config)
60
67
  end
61
68
 
@@ -63,10 +70,48 @@ module OutputMode
63
70
  # @see OutputMode::Outputs::Base#render
64
71
  # @see https://github.com/piotrmurach/tty-table
65
72
  def render(*data)
66
- table = TTY::Table.new header: header
67
- data.each { |d| table << generate(d) }
73
+ table = TTY::Table.new header: processed_header
74
+ data.each { |d| table << process_row(d) }
68
75
  table.render(renderer, **config, &block) || ''
69
76
  end
77
+
78
+ private
79
+
80
+ # Colorizes the header when requested
81
+ def processed_header
82
+ callables.map do |callable|
83
+ header = callable.config.fetch(:header, '')
84
+ color = callable.config.fetch(:header_color) || header_color
85
+ case color
86
+ when nil
87
+ header.to_s
88
+ when Array
89
+ pastel.decorate(header.to_s, *color)
90
+ else
91
+ pastel.decorate(header.to_s, color)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Colorizes the row when requested
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
101
+ case color
102
+ when NilClass
103
+ d.to_s
104
+ when Array
105
+ pastel.decorate(d.to_s, *color)
106
+ else
107
+ pastel.decorate(d.to_s, color)
108
+ end
109
+ end
110
+ end
111
+
112
+ def pastel
113
+ @pastel ||= Pastel::Color.new(enabled: colorize)
114
+ end
70
115
  end
71
116
  end
72
117
  end
@@ -38,13 +38,25 @@ 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.map do |callable, padding:|
53
+ value = callable.generator(output).call(model)
54
+ field = callable.config[:header]
55
+ [value, {field: field, padding: padding }]
47
56
  end
57
+
58
+ # Runs the provided block
59
+ objs.each(&block)
48
60
  end
49
61
 
50
62
  # Renders an ERB object within the entry's context. This provides access to the
@@ -61,6 +73,12 @@ module OutputMode
61
73
  def pastel
62
74
  @pastel ||= Pastel.new(enabled: colorize)
63
75
  end
76
+
77
+ private
78
+
79
+ def generated
80
+ @generated ||= output.generate(model)
81
+ end
64
82
  end
65
83
 
66
84
  # @!attribute [r] erb
@@ -68,7 +86,8 @@ module OutputMode
68
86
  # @!attribute [r] separator
69
87
  # @!attribute [r] fields
70
88
  # @!attribute [r] colorize
71
- attr_reader :erb, :fields, :separator, :colorize
89
+ # @!attribute [r] sections
90
+ attr_reader :erb, :fields, :separator, :colorize, :sections
72
91
 
73
92
  # Create a new +output+ which will render using +ERB+. The provided +template+ should
74
93
  # only render the +output+ for a single +entry+ (aka model, record, data object, etc).
@@ -84,23 +103,32 @@ module OutputMode
84
103
  #
85
104
  # @overload initialize(*procs, template: nil, fields: nil, seperator: "\n", yes: 'true', no: 'false', **config)
86
105
  # @param [Array] *procs see {OutputMode::Output#initialize}
87
- # @param [String] template: A string to be converted into +ERB+
88
106
  # @param [ERB] template: The +template+ object used by the renderer
89
107
  # @param [Array] fields: An optional array of field headers that map to the procs, repeating the last value if required
90
108
  # @param fields: A static value to use as all field headers
91
109
  # @param separator: The character(s) used to join the "entries" together
92
110
  # @param colorize: Flags if the caller wants the colorized version, this maybe ignored by +template+
111
+ # @param sections: An optional array that groups the procs into sections. This is ignored by default
93
112
  # @param [Hash] **config see {OutputMode::Output#initialize}
94
113
  def initialize(*procs,
95
114
  template: nil,
96
115
  fields: nil,
97
116
  separator: "\n",
98
117
  colorize: false,
118
+ sections: nil,
99
119
  **config)
100
- @erb = DEFAULT_ERB
120
+ @erb = case template
121
+ when String
122
+ ERB.new(template, nil, '-')
123
+ when ERB
124
+ template
125
+ else
126
+ DEFAULT_ERB
127
+ end
101
128
  @fields = fields
102
129
  @separator = separator
103
130
  @colorize = colorize
131
+ @sections = sections
104
132
  super(*procs, **config)
105
133
  end
106
134
 
@@ -1,7 +1,31 @@
1
1
  #==============================================================================
2
- # Refer to LICENSE.txt for licensing terms
2
+ # Copyright 2020 William McCumstie
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ # POSSIBILITY OF SUCH DAMAGE.
3
25
  #==============================================================================
4
26
 
27
+ require 'tty-color'
28
+
5
29
  module OutputMode
6
30
  module TLDR
7
31
  module Index
@@ -11,9 +35,18 @@ module OutputMode
11
35
  # @overload register_callable(header:, verbose: true)
12
36
  # @param header: The column's header field when displaying to humans
13
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
39
+ # @param header_color: Override the default color for the header
40
+ # @param row_color: Override the default color for the row
41
+ # @param modes: Additional modes flags for the callable
14
42
  # @yieldparam model The subject the column is describing, some sort of data model
15
- def register_callable(header:, verbose: nil, &b)
16
- super(modes: { verbose: verbose }, header: header, &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)
17
50
  end
18
51
  alias_method :register_column, :register_callable
19
52
 
@@ -21,6 +54,12 @@ module OutputMode
21
54
  # +$stdout+ as part of it's output class discovery logic. It does not
22
55
  # print to the output directly
23
56
  #
57
+ # The +ascii+ flag disables the unicode formatting in interactive shells.
58
+ # Non interactive shells use ASCII by default.
59
+ #
60
+ # The +verbose+ flag toggles the simplified and verbose outputs in the
61
+ # interactive output. Non-interactive outputs are always verbose
62
+ #
24
63
  # If +$stdout+ is an interactive shell (aka a TTY), then it will display using
25
64
  # {OutputMode::Outputs::Tabulated}. This is intended for human consumption
26
65
  # and will obey the provided +verbose+ flag.
@@ -29,26 +68,57 @@ module OutputMode
29
68
  # {OutputMode::Outputs::Delimited} using tab delimiters. This is intended
30
69
  # for consumption by machines. This output ignores the provided +verbose+
31
70
  # flag as it is always verbose.
32
- def build_output(verbose: false)
33
- callables = if verbose || !$stdout.tty?
71
+ #
72
+ # An interative/ non-interactive output can be forced by setting the
73
+ # +interactive+ flag to +true+/+false+ respectively
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
34
84
  # Filter out columns that are explicitly not verbose
35
- output_callables.select(&:verbose!)
85
+ output_callables.select { |o| o.verbose?(true) }
36
86
  else
37
87
  # Filter out columns that are explicitly verbose
38
88
  output_callables.reject(&:verbose?)
39
89
  end
40
90
 
41
- if $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
42
100
  # Creates the human readable output
101
+ opts = if ascii
102
+ { yes: 'yes', no: 'no', renderer: :ascii }
103
+ else
104
+ {
105
+ yes: '✓', no: '✕', renderer: :unicode, colorize: TTY::Color.color?,
106
+ header_color: header_color,
107
+ row_color: row_color
108
+ }
109
+ end
110
+
43
111
  Outputs::Tabulated.new(*callables,
44
- header: callables.map { |c| c.config.fetch(:header, 'missing') },
45
- renderer: :unicode,
112
+ rotate: false,
46
113
  padding: [0,1],
47
114
  default: '(none)',
48
- yes: '✓', no: '✕')
115
+ context: context,
116
+ **opts
117
+ )
49
118
  else
50
119
  # Creates the machine readable output
51
- Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'y', no: 'n', default: '')
120
+ Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: nil,
121
+ context: context)
52
122
  end
53
123
  end
54
124
  end
@@ -1,5 +1,27 @@
1
1
  #==============================================================================
2
- # Refer to LICENSE.txt for licensing terms
2
+ # Copyright 2020 William McCumstie
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ # POSSIBILITY OF SUCH DAMAGE.
3
25
  #==============================================================================
4
26
 
5
27
  require 'tty-color'
@@ -13,9 +35,16 @@ module OutputMode
13
35
  # @overload register_callable(header:, verbose: true)
14
36
  # @param header: The human readable key to the field, uses the term 'header' for consistency
15
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
16
41
  # @yieldparam model The subject the column is describing, some sort of data model
17
- def register_callable(header:, verbose: nil, &b)
18
- super(modes: { verbose: verbose }, header: header, &b)
42
+ def register_callable(modes: {}, header:, verbose: nil, interactive: nil, section: :other, &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)
19
48
  end
20
49
  alias_method :register_attribute, :register_callable
21
50
 
@@ -23,6 +52,12 @@ module OutputMode
23
52
  # +$stdout+ as part of it's output class discovery logic. It does not
24
53
  # print to the io directly
25
54
  #
55
+ # The +ascii+ flag disables the unicode formatting in interactive shells.
56
+ # Non interactive shells use ASCII by default.
57
+ #
58
+ # The +verbose+ flag toggles the simplified and verbose outputs in the
59
+ # interactive output. Non-interactive outputs are always verbose
60
+ #
26
61
  # If +$stdout+ is an interactive shell (aka a TTY), then it will display using
27
62
  # {OutputMode::Outputs::Templated}. This is intended for human consumption
28
63
  # and will obey the provided +verbose+ flag.
@@ -31,25 +66,57 @@ module OutputMode
31
66
  # {OutputMode::Outputs::Delimited} using tab delimiters. This is intended
32
67
  # for consumption by machines. This output ignores the provided +verbose+
33
68
  # flag as it is always verbose.
34
- def build_output(verbose: false)
35
- callables = if verbose || !$stdout.tty?
69
+ #
70
+ # The +template+ overrides the default erb template for the output
71
+ #
72
+ # An interative/ non-interactive output can be forced by setting the
73
+ # +interactive+ flag to +true+/+false+ respectively
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
36
84
  # Filter out columns that are explicitly not verbose
37
- output_callables.select(&:verbose!)
85
+ output_callables.select { |o| o.verbose?(true) }
38
86
  else
39
87
  # Filter out columns that are explicitly verbose
40
88
  output_callables.reject(&:verbose?)
41
89
  end
42
90
 
43
- if $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
44
100
  # Creates the human readable output
101
+ opts = if ascii
102
+ { yes: 'yes', no: 'no', colorize: false }
103
+ else
104
+ { yes: '✓', no: '✕', colorize: TTY::Color.color? }
105
+ end
106
+
107
+ sections = callables.map { |o| o.config[:section] }
108
+
45
109
  Outputs::Templated.new(*callables,
46
110
  fields: callables.map { |c| c.config.fetch(:header, 'missing') },
47
- colorize: TTY::Color.color?,
48
111
  default: '(none)',
49
- yes: '✓', no: '✕')
112
+ sections: sections,
113
+ template: template,
114
+ context: context,
115
+ **opts)
50
116
  else
51
117
  # Creates the machine readable output
52
- Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'y', no: 'n', default: '')
118
+ Outputs::Delimited.new(*callables, col_sep: "\t", yes: 'yes', no: 'no', default: nil,
119
+ context: context)
53
120
  end
54
121
  end
55
122
  end
@@ -25,5 +25,5 @@
25
25
  #==============================================================================
26
26
 
27
27
  module OutputMode
28
- VERSION = "1.1.1"
28
+ VERSION = "1.5.1"
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.1.1
4
+ version: 1.5.1
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-08-21 00:00:00.000000000 Z
11
+ date: 2021-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-table