report_print 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c0d7f2a1582aea408d911f88210e1b8c8ed606ce89f7bf21a7c7c26f27bb6bb7
4
+ data.tar.gz: dc9f17860c7ef6e576b06215ea6e8a5b1d605d2c7b6c115b8d951ad077b0d806
5
+ SHA512:
6
+ metadata.gz: 30dde24a45f9e1207ec2bccdd747e5ffe74a197284226ec0d60d29cf94e56bf5b818ed63f632a3703746d13c0ee0b1b76a00af51dea2f6daf20430c2bd1c77a3
7
+ data.tar.gz: 12847ec207fc9537d16464ab39a1f27d3c034b1fe26ee74320786f17670893e3653849694bd090a7294d09a98a30c041f9d88567c9e240cd893ba5bb45d5ae1e
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # ReportPrint
2
+
3
+ A hybrid between PrettyPrint and AwesomePrint/AmazingPrint. Providing you both a stylish readable default, and the ability to customize it as needed.
4
+
5
+ The name ReportPrint comes from the desire to output detailed reports of the program state at a given point in time, rather than merely inspecting an object.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```bash
12
+ bundle add report_print
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```bash
18
+ gem install report_print
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'report_print'
25
+
26
+ rp object
27
+ ```
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/BenoitHiller/report_print.
@@ -0,0 +1,127 @@
1
+ using ReportPrint::Refinements
2
+
3
+ class ::Object < ::BasicObject
4
+ def report_print(rp)
5
+ rp.unless_seen(self) do
6
+ rp.write_header(self)
7
+ rp.multiline(after: rp.color("end", :bright_blue), after_empty: false) do
8
+ rp.write_instance_variables(self)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ class ::Module < ::Object
15
+ def report_print(rp)
16
+ rp.write(name.short_class_name)
17
+ end
18
+ end
19
+
20
+ class ::Symbol < ::Object
21
+ report_print_inspect!(color: :bright_blue)
22
+ end
23
+
24
+ class ::TrueClass < ::Object
25
+ report_print_inspect!(color: :bright_blue)
26
+ end
27
+
28
+ class ::FalseClass < ::Object
29
+ report_print_inspect!(color: :bright_blue)
30
+ end
31
+
32
+ class ::Numeric < ::Object
33
+ report_print_inspect!(color: :bright_magenta)
34
+ end
35
+
36
+ class ::String < ::Object
37
+ report_print_inspect!(color: :bright_green)
38
+ end
39
+
40
+ class ::NilClass < ::Object
41
+ report_print_inspect!(color: :bright_blue)
42
+ end
43
+
44
+ class ::Array < ::Object
45
+ def report_print(rp)
46
+ rp.write("[")
47
+ rp.multiline(after: "]", separator: ",") do
48
+ to_a.each do |item|
49
+ rp.rp(item)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ class ::Hash < ::Object
56
+ def report_print(rp)
57
+ rp.write("{")
58
+ rp.multiline(after: "}", separator: ",") do
59
+ each do |key, value|
60
+ rp.inline(" ") do
61
+ case key
62
+ in Symbol
63
+ rp.write(key, ":", color: :bright_cyan)
64
+ else
65
+ rp.rp(key)
66
+ rp.write("=>")
67
+ end
68
+
69
+ rp.rp(value)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ class ::Set < ::Object
77
+ def report_print(rp)
78
+ rp.inline("") do
79
+ rp.write("Set", color: :yellow)
80
+ rp.write("[")
81
+ end
82
+ rp.multiline(after: "]", separator: ",") do
83
+ to_a.each do |item|
84
+ rp.rp(item)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ class ::Data < ::Object
91
+ def report_print(rp)
92
+ name = self.class.short_class_name
93
+ rp.inline("") do
94
+ rp.write(name, color: :blue)
95
+ rp.write("[")
96
+ end
97
+ rp.multiline(after: "]", separator: ",") do
98
+ self.to_h.each do |name, value|
99
+ rp.inline(": ") do
100
+ rp.write(name, color: :bright_cyan)
101
+ rp.rp(value)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ class ::Struct < ::Object
109
+ def report_print(rp)
110
+ name = self.class.short_class_name
111
+ rp.inline(" ") do
112
+ rp.inline("") do
113
+ rp.write(name, color: :bright_yellow)
114
+ rp.write("(")
115
+ end
116
+ rp.write_object_id(self)
117
+ end
118
+ rp.multiline(after: ")", separator: ",") do
119
+ self.to_h.each do |name, value|
120
+ rp.inline(": ") do
121
+ rp.write(name, color: :bright_cyan)
122
+ rp.rp(value)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,21 @@
1
+ module ReportPrint
2
+ module Dsl
3
+ ##
4
+ # Render out the specified `object` using its Object#report_print method.
5
+ #
6
+ # Arguments:
7
+ #
8
+ # * `output:` specifies where the output should be written to, by default
9
+ # it is `$>`, meaning standard output.
10
+ # * `color:` specifies whether the text should render colored output or
11
+ # not. The default value of `:auto` indicates that it should use the
12
+ # value of `output.tty?` to determine if color output is enabled or not.
13
+ def rp(object, output: $>, color: :auto)
14
+ printer = Printer.new(output, color:)
15
+ printer.rp(object)
16
+ output.write("\n")
17
+
18
+ nil
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,363 @@
1
+ require "rainbow"
2
+ using ReportPrint::Refinements
3
+
4
+ module ReportPrint
5
+ ##
6
+ # The class of the printer instances provided to Object#report_print methods.
7
+ class Printer
8
+ State = Data.define( # :nodoc:
9
+ :mode,
10
+ :indent,
11
+ :separator,
12
+ :inline_separator,
13
+ :next_inline_separator,
14
+ :after,
15
+ :start_of_line,
16
+ :start_of_block
17
+ ) do
18
+ # Ruby type checking can't really handle this type of dynamic programming
19
+ # very well yet, so we just skip type checking here as there is only the
20
+ # one short function.
21
+ # steep:ignore:start
22
+ def inline?
23
+ mode == :inline
24
+ end
25
+ # steep:ignore:end
26
+ end
27
+
28
+ ##
29
+ # Returns a new printer object writing to the specified `output`.
30
+ #
31
+ # **Intended only for internal use.** See Dsl#rp for more details.
32
+ def initialize(output = $>, color: :auto)
33
+ @output = output
34
+ if color == :auto
35
+ # Ignore block performing runtime type check.
36
+ # steep:ignore:start
37
+ color = output.respond_to?(:tty?) && output.tty?
38
+ # steep:ignore:end
39
+ end
40
+ @rainbow = Rainbow::Wrapper.new(color)
41
+
42
+ @seen = ::Set.new
43
+ @state = State[
44
+ mode: :multiline,
45
+ indent: 0,
46
+ separator: "",
47
+ inline_separator: "",
48
+ next_inline_separator: nil,
49
+ after: nil,
50
+ start_of_line: false,
51
+ start_of_block: true
52
+ ]
53
+ end
54
+
55
+ ##
56
+ # Print an `object` using its Object#report_print method.
57
+ #
58
+ # Note: if an argument is provided which does not inherit from Object, then
59
+ # `"BasicObject"` will be printed.
60
+ def rp(object)
61
+ case object
62
+ when Object
63
+ object.report_print(self)
64
+ else
65
+ write("BasicObject", color: :bright_yellow)
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Wrap the provided input string in a `Rainbow::Presenter` and color it using
71
+ # `Rainbow::Presenter#color` passing the provided values.
72
+ #
73
+ # This uses a `Rainbow::Wrapper` instantiated by the Printer which uses the
74
+ # user specified settings for whether color printing is enabled.
75
+ #
76
+ # Additionally if `values` is a single symbol matching `/bright_(\w+)/`,
77
+ # then it will first apply the specified color, then apply
78
+ # `Rainbow::Presenter#bright` to the output.
79
+ #
80
+ # Thus the following are equivalent:
81
+ #
82
+ # ```
83
+ # rp.color("string", :bright_blue)
84
+ # rp.color("string", :blue).bright
85
+ # ```
86
+ def color(input, *values)
87
+ wrapped = @rainbow.wrap(input.to_s)
88
+ case values
89
+ in [Symbol => name]
90
+ if (matches = name.match(/bright_(\w+)/))
91
+ wrapped.color(matches[1].to_sym).bright
92
+ else
93
+ wrapped.color(name)
94
+ end
95
+ in []
96
+ wrapped
97
+ else
98
+ wrapped.color(*values)
99
+ end
100
+ end
101
+
102
+ ##
103
+ # Define a block which prints each item on the same line separated by
104
+ # `inline_separator`.
105
+ #
106
+ # If called inside another #inline block, it only customize the separator
107
+ # between the elements printed within the block.
108
+ #
109
+ # If no `inline_separator` is specified, then the `inline_separator` of the
110
+ # containing inline block will be used, or `""` if there is no containing
111
+ # inline block.
112
+ #
113
+ # If you want to reset the separator to the empty value you will thus need
114
+ # to pass `nil` or `""`.
115
+ def inline(inline_separator = :inherit, &block)
116
+ if inline_separator == :inherit
117
+ inline_separator = @state.inline_separator
118
+ end
119
+ result = with_state(mode: :inline, inline_separator:, &block)
120
+ if @state.inline?
121
+ set_state(
122
+ start_of_line: @state.start_of_line && result.start_of_line,
123
+ start_of_block: @state.start_of_block && result.start_of_block
124
+ )
125
+ if @state.next_inline_separator != result.next_inline_separator
126
+ # If the inner inline state is requesting a separator, then that
127
+ # means we should request the current separator instead.
128
+ set_state(next_inline_separator: @state.inline_separator)
129
+ end
130
+ else
131
+ set_state(
132
+ start_of_block: @state.start_of_block && result.start_of_block
133
+ )
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Define a block which prints each item or #inline block on a separate line.
139
+ #
140
+ # Accepts the following options:
141
+ #
142
+ # * `separator:` a separator to print before each newline, or `:inherit` to
143
+ # use the separator of the containing multiline block.
144
+ # * `after:` a string to print after the end of the block.
145
+ # * `after_empty:` if true, print `after:` even when nothing was written
146
+ # inside the block.
147
+ # * `indent:` the number of spaces to increase the indent by inside the block.
148
+ def multiline(
149
+ separator: nil,
150
+ after: nil,
151
+ after_empty: true,
152
+ indent: 2,
153
+ &block
154
+ )
155
+ if separator == :inherit
156
+ separator = @state.separator
157
+ end
158
+
159
+ result = with_state(
160
+ mode: :multiline,
161
+ start_of_line: true,
162
+ start_of_block: true,
163
+ indent: @state.indent + indent,
164
+ next_inline_separator: nil,
165
+ separator:,
166
+ &block
167
+ )
168
+
169
+ set_state(
170
+ start_of_block: @state.start_of_block && result.start_of_block
171
+ )
172
+
173
+ did_write = !result.start_of_block
174
+
175
+ if did_write || after_empty
176
+ unless after.nil?
177
+ if did_write
178
+ break_line
179
+ end
180
+ @output.write(after)
181
+ set_state(start_of_block: false)
182
+
183
+ did_write = true
184
+ end
185
+ end
186
+
187
+ if did_write
188
+ if @state.inline?
189
+ set_state(next_inline_separator: @state.inline_separator)
190
+ else
191
+ # This is to cleanup the special case handling of the outermost state.
192
+ # Likely this would be more clear with an explicit flag.
193
+ set_state(start_of_line: true)
194
+ end
195
+ end
196
+ end
197
+
198
+ ##
199
+ # Write out the provided `strings` given the current printer state.
200
+ #
201
+ # If `color:` is provided it will be interpreted the same as in the case
202
+ # where #color is given a singular argument.
203
+ #
204
+ # When multiple strings are provided, they will be rendered as if they were
205
+ # first concatenated into a single string, as in `IO#write`. Meaning no
206
+ # separators or line breaks will be rendered between them.
207
+ def write(*strings, color: nil)
208
+ if @state.start_of_line
209
+ unless @state.start_of_block
210
+ @output.write(@state.separator)
211
+ end
212
+ break_line
213
+ elsif @state.next_inline_separator
214
+ @output.write(@state.next_inline_separator)
215
+ end
216
+
217
+ if color
218
+ strings = strings.map do |string|
219
+ self.color(string, color)
220
+ end
221
+ end
222
+
223
+ @output.write(*strings)
224
+
225
+ if @state.inline?
226
+ set_state(
227
+ start_of_line: false,
228
+ start_of_block: false,
229
+ next_inline_separator: @state.inline_separator
230
+ )
231
+ else
232
+ set_state(
233
+ start_of_block: false,
234
+ start_of_line: true
235
+ )
236
+ end
237
+ end
238
+
239
+ ##
240
+ # Write the class name and the object id joined with a space.
241
+ #
242
+ # <pre>
243
+ # <span style="color:var(--code-orange)"
244
+ # >Object</span> <span style="color:var(--code-gray)"
245
+ # >0xabcd1234</span>
246
+ # </pre>
247
+ def write_header(object, write_id: true)
248
+ inline(" ") do
249
+ write(object.class.short_class_name, color: :bright_yellow)
250
+ if write_id
251
+ write_object_id(object)
252
+ end
253
+ end
254
+ end
255
+
256
+ ##
257
+ # Write the id of the object as hex.
258
+ #
259
+ # <pre>
260
+ # <span style="color:var(--code-gray)">0xabcd1234</span>
261
+ # </pre>
262
+ def write_object_id(object)
263
+ write(sprintf("%#x", object.__id__), color: :bright_black)
264
+ end
265
+
266
+ ##
267
+ # Write out the instance variables of an object.
268
+ #
269
+ # If an array of symbols is provided as `variables`, only those will be
270
+ # considered, otherwise all variables returned by Object#instance_variables
271
+ # will be used.
272
+ #
273
+ # ```
274
+ # class Example
275
+ # def new(value)
276
+ # @value = value
277
+ # @string = value.to_s
278
+ # end
279
+ # end
280
+ #
281
+ # rp.write_instance_variables(Example.new(1))
282
+ # rp.write_instance_variables(Example.new(:two), [:@value])
283
+ # ```
284
+ #
285
+ # Output:
286
+ #
287
+ # <pre>
288
+ # <span style="color:var(--code-cyan)"
289
+ # >@value</span> = <span style="color:var(--code-purple)"
290
+ # >1</span>
291
+ # <span style="color:var(--code-cyan)"
292
+ # >@string</span> = <span style="color:var(--code-green)"
293
+ # >"1"</span>
294
+ # <span style="color:var(--code-cyan)"
295
+ # >@value</span> = <span style="color:var(--code-blue)"
296
+ # >:two</span>
297
+ # </pre>
298
+ def write_instance_variables(object, variables = :all)
299
+ if variables == :all
300
+ variables = object.instance_variables
301
+ end
302
+
303
+ variables.each do |name|
304
+ inline(" ") do
305
+ write(name, color: :bright_cyan)
306
+ write("=")
307
+ rp(object.instance_variable_get(name))
308
+ end
309
+ end
310
+ end
311
+
312
+ def unless_seen(object)
313
+ if @seen.include?(object.__id__)
314
+ write_header(object)
315
+ else
316
+ @seen.add(object.__id__)
317
+ yield
318
+ end
319
+ end
320
+
321
+ private
322
+
323
+ def write_after(value)
324
+ if @state.start_of_line
325
+ break_line
326
+ end
327
+
328
+ @output.write(value)
329
+
330
+ if @state.inline?
331
+ set_state(
332
+ start_of_line: false,
333
+ start_of_block: false,
334
+ next_inline_separator: @state.inline_separator
335
+ )
336
+ else
337
+ set_state(
338
+ start_of_block: false,
339
+ start_of_line: true
340
+ )
341
+ end
342
+ end
343
+
344
+ def break_line
345
+ @output.write("\n")
346
+ @output.write(" " * @state.indent)
347
+ end
348
+
349
+ def with_state(**kwargs)
350
+ previous = @state
351
+ @state = previous.with(**kwargs)
352
+
353
+ yield
354
+
355
+ result, @state = @state, previous
356
+ result
357
+ end
358
+
359
+ def set_state(**kwargs)
360
+ @state = @state.with(**kwargs)
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,18 @@
1
+ module ReportPrint
2
+ module Refinements
3
+ refine Object.singleton_class do
4
+ def report_print_inspect!(color: nil)
5
+ define_method(:report_print) do |rp|
6
+ # @type self: Object
7
+ rp.write(inspect, color:)
8
+ end
9
+ end
10
+ end
11
+
12
+ refine Module do
13
+ def short_class_name
14
+ name.sub(/^.*::/, "")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module ReportPrint
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "report_print/version"
2
+ require_relative "report_print/refinements"
3
+ require_relative "report_print/printer"
4
+ require_relative "report_print/core_extensions"
5
+ require_relative "report_print/dsl"
6
+
7
+ module ReportPrint; end
8
+
9
+ # :enddoc:
10
+ Kernel.prepend(ReportPrint::Dsl)
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: report_print
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Benoit Hiller
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rainbow
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.1'
26
+ description: 'A hybrid between PrettyPrint and AwesomePrint/AmazingPrint. Providing
27
+ you both a stylish readable default, and the ability to customize it as needed.
28
+
29
+ '
30
+ email:
31
+ - benoit.hiller@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.md
37
+ - lib/report_print.rb
38
+ - lib/report_print/core_extensions.rb
39
+ - lib/report_print/dsl.rb
40
+ - lib/report_print/printer.rb
41
+ - lib/report_print/refinements.rb
42
+ - lib/report_print/version.rb
43
+ homepage: https://github.com/BenoitHiller/report_print
44
+ licenses: []
45
+ metadata:
46
+ allowed_push_host: https://rubygems.org
47
+ homepage_uri: https://github.com/BenoitHiller/report_print
48
+ source_code_uri: https://github.com/BenoitHiller/report_print
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.2.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 4.0.10
64
+ specification_version: 4
65
+ summary: A hybrid between PrettyPrint and AwesomePrint/AmazingPrint
66
+ test_files: []