output_mode 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ #==============================================================================
2
+ # Refer to LICENSE.txt for licensing terms
3
+ #==============================================================================
4
+
5
+ require 'erb'
6
+
7
+ module OutputMode
8
+ DEFAULT_ERB = ERB.new(<<~TEMPLATE, nil, '-')
9
+ <% each do |value, field:, padding:, **_| -%>
10
+ <% if value.nil? && field.nil? -%>
11
+
12
+ <% elsif field.nil? -%>
13
+ <%= pastel.bold '*' -%> <%= pastel.green value %>
14
+ <% else -%>
15
+ <%= padding -%><%= pastel.blue.bold field -%><%= pastel.bold ':' -%> <%= pastel.green value %>
16
+ <% end -%>
17
+ <% end -%>
18
+ TEMPLATE
19
+ end
@@ -0,0 +1,30 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ module OutputMode
28
+ class Error < StandardError; end
29
+ end
30
+
@@ -0,0 +1,127 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ module OutputMode
28
+ # @abstract Defines the public interface to all subclasses
29
+ #
30
+ # Base outputting class that wraps an array of procs or other
31
+ # callable object. Each implementation must override the {#render} method
32
+ # so it returns an array.
33
+ class Output
34
+ # @!attribute [r] procs
35
+ # @return [Array<#call>] the callable methods to generate output
36
+ # @!attribute [r] config
37
+ # @return [Hash] additional key-values to modify the render
38
+ # @!attribute [r] default
39
+ # @return either a static default or a column based array of defaults
40
+ # @!attribute [r] yes
41
+ # @return either a static yes value or a column based array of values
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
45
+
46
+ # Creates a new outputting instance from an array of procs
47
+ #
48
+ # @param *procs [Array<#call>] an array of procs (or callable objects)
49
+ # @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
+ # @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
+ # @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.
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
58
+ @config = config
59
+ @yes = yes
60
+ @no = no
61
+ @default = default
62
+ end
63
+
64
+ # Returns the results of the +procs+ for a particular +object+. It will apply the
65
+ # +default+, +yes+, and +no+ values.
66
+ 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
78
+ end
79
+ end
80
+
81
+ # @abstract It should be implemented by the subclass using the +generate+ method
82
+ # Renders the results of the procs into a string. Each data
83
+ # objects should be passed individual to each proc to generate the final
84
+ # output.
85
+ #
86
+ # The method must be overridden on all inherited classes
87
+ #
88
+ # @param *data [Array] a set of data to be rendered into the output
89
+ # @return [String] the output string
90
+ # @see #generate
91
+ def render(*data)
92
+ raise NotImplementedError
93
+ 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
+ end
127
+ end
@@ -0,0 +1,36 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ require 'output_mode/output'
28
+
29
+ module OutputMode
30
+ module Outputs
31
+ Dir.glob(File.expand_path('outputs/*.rb', __dir__)).each do |path|
32
+ autoload File.basename(path).chomp('.rb').capitalize, path
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,49 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ require 'csv'
28
+
29
+ module OutputMode
30
+ module Outputs
31
+ class Delimited < Output
32
+ # @return [Hash] additional options to CSV.new
33
+ # @see https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html
34
+ def config; super; end
35
+
36
+ # Implements the render method using +CSV+
37
+ #
38
+ # @see OutputMode::Output#render
39
+ # @see CSV
40
+ def render(*data)
41
+ io = StringIO.new
42
+ csv = CSV.new(io, **config)
43
+ data.each { |d| csv << generate(d) }
44
+ io.tap(&:rewind).read
45
+ end
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,73 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ require 'tty-table'
28
+
29
+ module OutputMode
30
+ module Outputs
31
+ class Tabulated < Output
32
+ attr_reader :renderer, :header, :default, :block, :yes, :no
33
+
34
+ # @!attribute [r] renderer
35
+ # @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
+ # @!attribute [r] block
39
+ # @return [#call] an optional block of code that configures the renderer
40
+
41
+ # @return [Hash] additional options to +TTY::Table+ renderer
42
+ # @see https://github.com/piotrmurach/tty-table#33-options
43
+ def config; super; end
44
+
45
+ # @overload initialize(*procs, renderer: nil, header: nil, **config)
46
+ # @param [Array] *procs see {OutputMode::Outputs::Base#initialize}
47
+ # @param [Symbol] :renderer override the default renderer
48
+ # @param [Array<String>] :header the header row of the table
49
+ # @param [Hash] **config additional options to the renderer
50
+ # @yieldparam tty_table_renderer [TTY::Table::Renderer::Base] optional access the underlining TTY::Table renderer
51
+ def initialize(*procs,
52
+ renderer: :unicode,
53
+ header: nil,
54
+ **config,
55
+ &block)
56
+ @header = header
57
+ @renderer = renderer
58
+ @block = block
59
+ super(*procs, **config)
60
+ end
61
+
62
+ # Implements the render method using +TTY::Table+
63
+ # @see OutputMode::Outputs::Base#render
64
+ # @see https://github.com/piotrmurach/tty-table
65
+ def render(*data)
66
+ table = TTY::Table.new header: header
67
+ data.each { |d| table << generate(d) }
68
+ table.render(renderer, **config, &block) || ''
69
+ end
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,128 @@
1
+ #==============================================================================
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.
25
+ #==============================================================================
26
+
27
+ require 'pastel'
28
+
29
+ require 'output_mode/default_erb'
30
+
31
+ module OutputMode
32
+ module Outputs
33
+ class Templated < Output
34
+ Entry = Struct.new(:output, :model, :colorize) do
35
+ include Enumerable
36
+
37
+ # @yieldparam value An attribute to be rendered
38
+ # @yieldparam field: An optional field header for the value
39
+ # @yieldparam padding: A padding string which will right align the +field+
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)
47
+ end
48
+ end
49
+
50
+ # Renders an ERB object within the entry's context. This provides access to the
51
+ # +output+, +model+, and +enumerable+ methods
52
+ #
53
+ # @param [ERB] erb the ERB object which contains the template to be rendered
54
+ # @return [String] the result text
55
+ def render(erb)
56
+ erb.result(binding)
57
+ end
58
+
59
+ # Library for colorizing the output. It is automatically disabled when the
60
+ # +colorize+ flag is +false+
61
+ def pastel
62
+ @pastel ||= Pastel.new(enabled: colorize)
63
+ end
64
+ end
65
+
66
+ # @!attribute [r] erb
67
+ # @return [ERB] The +erb+ object containing the template to be rendered.
68
+ # @!attribute [r] separator
69
+ # @!attribute [r] fields
70
+ # @!attribute [r] colorize
71
+ attr_reader :erb, :fields, :separator, :colorize
72
+
73
+ # Create a new +output+ which will render using +ERB+. The provided +template+ should
74
+ # only render the +output+ for a single +entry+ (aka model, record, data object, etc).
75
+ #
76
+ # The +template+ maybe either a +String+ or a +ERB+ object. Strings will automatically
77
+ # be converted to +ERB+ with the +trim_mode+ set to +-+.
78
+ #
79
+ # A default template will be used if one has not be provided.
80
+ #
81
+ # @see https://ruby-doc.org/stdlib-2.7.1/libdoc/erb/rdoc/ERB.html
82
+ # @see render
83
+ # @see DEFAULT_ERB
84
+ #
85
+ # @overload initialize(*procs, template: nil, fields: nil, seperator: "\n", yes: 'true', no: 'false', **config)
86
+ # @param [Array] *procs see {OutputMode::Output#initialize}
87
+ # @param [String] template: A string to be converted into +ERB+
88
+ # @param [ERB] template: The +template+ object used by the renderer
89
+ # @param [Array] fields: An optional array of field headers that map to the procs, repeating the last value if required
90
+ # @param fields: A static value to use as all field headers
91
+ # @param separator: The character(s) used to join the "entries" together
92
+ # @param colorize: Flags if the caller wants the colorized version, this maybe ignored by +template+
93
+ # @param [Hash] **config see {OutputMode::Output#initialize}
94
+ def initialize(*procs,
95
+ template: nil,
96
+ fields: nil,
97
+ separator: "\n",
98
+ colorize: false,
99
+ **config)
100
+ @erb = DEFAULT_ERB
101
+ @fields = fields
102
+ @separator = separator
103
+ @colorize = colorize
104
+ super(*procs, **config)
105
+ end
106
+
107
+ # Implements the render method using the ERB +template+. The +template+ will
108
+ # be rendered within the context of an +Entry+. An +Entry+ object will be
109
+ # created/ rendered for each element of +data+
110
+ #
111
+ # @see OutputMode::Output#render
112
+ def render(*data)
113
+ data.map { |d| Entry.new(self, d, colorize).render(erb) }
114
+ .join(separator)
115
+ end
116
+
117
+ # Returns the length of the maximum field
118
+ def max_field_length
119
+ if fields.is_a? Array
120
+ fields.map { |f| f.to_s.length }.max
121
+ else
122
+ fields.to_s.length
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+