output_mode 1.0.0

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.
@@ -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
+