cute_print 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  3. data/README.md +42 -1
  4. data/VERSION +1 -1
  5. data/cute_print.gemspec +25 -4
  6. data/features/call_chain.feature +6 -5
  7. data/features/configuring/configure_position_format.feature +1 -2
  8. data/features/inspect/core.feature +18 -0
  9. data/features/inspect/inspect_with_location.feature +1 -1
  10. data/features/support/env.rb +3 -3
  11. data/features/support/helpers/example.rb +11 -13
  12. data/features/support/helpers/example_runner.rb +24 -0
  13. data/features/support/helpers/fork_example_runner.rb +37 -0
  14. data/features/support/helpers/lib_path.rb +7 -0
  15. data/features/support/helpers/shell_example_runner.rb +26 -0
  16. data/features/support/step_definitions.rb +0 -6
  17. data/lib/cute_print/core.rb +3 -0
  18. data/lib/cute_print/core_ext.rb +2 -0
  19. data/lib/cute_print/cute_print.rb +83 -0
  20. data/lib/cute_print/format/inspect.rb +12 -0
  21. data/lib/cute_print/format/pretty_print.rb +17 -0
  22. data/lib/cute_print/format.rb +2 -0
  23. data/lib/cute_print/formatter.rb +15 -81
  24. data/lib/cute_print/inline_labeler.rb +36 -0
  25. data/lib/cute_print/labeler.rb +71 -0
  26. data/lib/cute_print/location.rb +4 -10
  27. data/lib/cute_print/location_label/filename.rb +19 -0
  28. data/lib/cute_print/location_label/path.rb +19 -0
  29. data/lib/cute_print/location_label.rb +23 -0
  30. data/lib/cute_print/mixin.rb +13 -12
  31. data/lib/cute_print/outline_labeler.rb +38 -0
  32. data/lib/cute_print/printer.rb +4 -36
  33. data/lib/cute_print/source_label.rb +26 -0
  34. data/lib/cute_print.rb +1 -25
  35. data/spec/format/inspect_spec.rb +20 -0
  36. data/spec/format/pretty_print_spec.rb +36 -0
  37. data/spec/inline_labeler_spec.rb +39 -0
  38. data/spec/labeler_spec.rb +97 -0
  39. data/spec/outline_labeler_spec.rb +37 -0
  40. metadata +24 -3
  41. data/lib/cute_print/default_printer.rb +0 -14
@@ -1,107 +1,56 @@
1
1
  require "pp"
2
2
  require "stringio"
3
3
 
4
+ require_relative "format"
5
+ require_relative "labeler"
4
6
  require_relative "location"
5
- require_relative "ruby_parser"
7
+ require_relative "location_label"
8
+ require_relative "source_label"
6
9
 
7
10
  module CutePrint
8
11
  # @api private
9
12
  class Formatter
10
13
 
11
14
  DEFAULT_WIDTH = 79
12
- DEFAULT_MAX_INLINE_LABEL_WIDTH = 40
13
15
 
14
16
  def initialize(opts = {})
15
- @inspector = :inspect
16
17
  @method = opts.fetch(:method)
17
18
  @out = opts.fetch(:out)
18
19
  @block = opts.fetch(:block, nil)
19
20
  @values = opts.fetch(:values, [])
20
21
  @width = opts.fetch(:width, DEFAULT_WIDTH)
21
- @max_inline_label_width = opts.fetch(:max_inline_label_width,
22
- DEFAULT_MAX_INLINE_LABEL_WIDTH)
23
22
  if @block && !@values.empty?
24
23
  raise ArgumentError, "arguments and block are mutually exclusive"
25
24
  end
25
+ @location_label = nil
26
26
  end
27
27
 
28
28
  def write
29
- if labeled_values.empty? && !label.empty?
29
+ if values.empty? && !label.empty?
30
30
  write_line label.chomp(": ")
31
31
  else
32
- labeled_values.each do |lines|
33
- write_lines lines
34
- end
35
- end
36
- end
37
-
38
- def inspected_values
39
- values.map do |value|
40
- @inspector.call(value, value_width)
41
- end
42
- end
43
-
44
- def labeled_values
45
- if label_width > @max_inline_label_width
46
- outline_labeled_values
47
- else
48
- inline_labeled_values
49
- end
50
- end
51
-
52
- def inline_labeled_values
53
- labeled_values = inspected_values.map do |lines|
54
- lines.map.with_index do |line, i|
55
- if i == 0
56
- label + line
57
- else
58
- indent + line
59
- end
60
- end
61
- end
62
- end
63
-
64
- def outline_labeled_values
65
- inspected_values.map do |lines|
66
- [label.rstrip] + lines.map do |line|
67
- " " + line
32
+ values.each do |value|
33
+ labeler = Labeler.new(@format, @width, label, value)
34
+ write_lines labeler.labeled
68
35
  end
69
36
  end
70
37
  end
71
38
 
72
39
  def with_location(format_key)
73
- @location_format = LOCATION_FORMATS.fetch(format_key) {
74
- raise ArgumentError, "Unknown location format: #{format_key.inspect}"
75
- }
76
- @location = Location.find
40
+ location = Location.find
41
+ @location_label = LocationLabel.make(format_key, location)
77
42
  end
78
43
 
79
44
  def inspect
80
- @inspector = ->(value, value_width) do
81
- [value.inspect]
82
- end
45
+ @format = Format::Inspect.new
83
46
  end
84
47
 
85
48
  def pretty_print
86
- @inspector = ->(value, value_width) do
87
- out = StringIO.new
88
- PP.pp(value, out, value_width)
89
- out.string.lines
90
- end
49
+ @format = Format::PrettyPrint.new
91
50
  end
92
51
 
93
52
  private
94
53
 
95
- LOCATION_FORMATS = {
96
- filename: "%<filename>s:%<line_number>d: ",
97
- path: "%<path>s:%<line_number>d: ",
98
- }
99
- private_constant :LOCATION_FORMATS
100
-
101
- def value_width
102
- @width - label_width
103
- end
104
-
105
54
  def values
106
55
  if @block
107
56
  [@block.call]
@@ -125,27 +74,12 @@ module CutePrint
125
74
  @label ||= make_label
126
75
  end
127
76
 
128
- def label_width
129
- label.size
130
- end
131
-
132
- def indent
133
- " " * label_width
134
- end
135
-
136
77
  def make_label
137
78
  [
138
- (@location.format(@location_format) if @location),
139
- ("#{block_code} is " if @block),
79
+ (@location_label.to_s if @location_label),
80
+ (SourceLabel.new(@block, @method) if @block),
140
81
  ].compact.join
141
82
  end
142
83
 
143
- def block_code
144
- ruby_parser = RubyParser.from_block(@block)
145
- parsed_code = ruby_parser.parse
146
- method_call = parsed_code.first_call_to_method(@method)
147
- method_call.block.to_ruby
148
- end
149
-
150
84
  end
151
85
  end
@@ -0,0 +1,36 @@
1
+ module CutePrint
2
+ # @api private
3
+ class InlineLabeler
4
+
5
+ def self.label(formatter, width, label, value)
6
+ new(formatter, width, label, value).labeled
7
+ end
8
+
9
+ def initialize(formatter, width, label, value)
10
+ @formatter = formatter
11
+ @width = width
12
+ @label = label
13
+ @value = value
14
+ end
15
+
16
+ def labeled
17
+ [@label.rstrip + "\n"] + indented_lines
18
+ end
19
+
20
+ private
21
+
22
+ INDENT = ' '
23
+ private_constant :INDENT
24
+
25
+ def indented_lines
26
+ lines.map do |line|
27
+ INDENT + line
28
+ end
29
+ end
30
+
31
+ def lines
32
+ @formatter.format(@width - INDENT.size, @value)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ require_relative "inline_labeler"
2
+ require_relative "outline_labeler"
3
+
4
+ module CutePrint
5
+ # @api private
6
+ class Labeler
7
+
8
+ def self.label(format, width, label, value)
9
+ new(format, width, label, value).labeled
10
+ end
11
+
12
+ def initialize(format, width, label, value)
13
+ @format = format
14
+ @width = width
15
+ @label = label
16
+ @value = value
17
+ end
18
+
19
+ def labeled
20
+ # Optimization: Avoid the computation of "outlined", and the
21
+ # elaborate comparison, in many cases.
22
+ return outlined if outlined_fits_on_one_line?
23
+ inlined_fits = lines_fit_horizontally?(inlined)
24
+ outlined_fits = lines_fit_horizontally?(outlined)
25
+ if inlined_fits && outlined_fits
26
+ [inlined, outlined].min_by do |lines|
27
+ [
28
+ lines.size,
29
+ lines.equal?(outlined) ? 0 : 1,
30
+ ]
31
+ end
32
+ elsif inlined_fits && !outlined_fits
33
+ inlined
34
+ elsif !inlined_fits && outlined_fits
35
+ outlined
36
+ else # neither fits
37
+ [inlined, outlined].min_by do |lines|
38
+ [
39
+ lines.map(&:size).min,
40
+ lines.size,
41
+ ]
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def lines_fit_horizontally?(lines)
49
+ lines.all? do |line|
50
+ line_fits_horizontally?(line)
51
+ end
52
+ end
53
+
54
+ def line_fits_horizontally?(line)
55
+ line.chomp.size <= @width
56
+ end
57
+
58
+ def outlined_fits_on_one_line?
59
+ outlined.size == 1 && line_fits_horizontally?(outlined.first)
60
+ end
61
+
62
+ def outlined
63
+ @outlined ||= OutlineLabeler.label(@format, @width, @label, @value)
64
+ end
65
+
66
+ def inlined
67
+ @inlined ||= InlineLabeler.label(@format, @width, @label, @value)
68
+ end
69
+
70
+ end
71
+ end
@@ -1,6 +1,7 @@
1
1
  require_relative "finds_foreign_caller"
2
2
 
3
3
  module CutePrint
4
+ # @api private
4
5
  class Location
5
6
 
6
7
  extend FindsForeignCaller
@@ -11,21 +12,14 @@ module CutePrint
11
12
  new(path, line_number)
12
13
  end
13
14
 
15
+ attr_reader :path
16
+ attr_reader :line_number
17
+
14
18
  def initialize(path, line_number)
15
19
  @path = path
16
20
  @line_number = line_number
17
21
  end
18
22
 
19
- def format(template)
20
- template % {
21
- path: @path,
22
- filename: filename,
23
- line_number: @line_number,
24
- }
25
- end
26
-
27
- private
28
-
29
23
  def filename
30
24
  File.basename(@path)
31
25
  end
@@ -0,0 +1,19 @@
1
+ module CutePrint
2
+ class LocationLabel
3
+ # @api private
4
+ class Filename
5
+
6
+ def initialize(location)
7
+ @location = location
8
+ end
9
+
10
+ def to_s
11
+ "%s:%d: " % [
12
+ @location.filename,
13
+ @location.line_number,
14
+ ]
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module CutePrint
2
+ class LocationLabel
3
+ # @api private
4
+ class Path
5
+
6
+ def initialize(location)
7
+ @location = location
8
+ end
9
+
10
+ def to_s
11
+ "%s:%d: " % [
12
+ @location.path,
13
+ @location.line_number,
14
+ ]
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "location_label/filename"
2
+ require_relative "location_label/path"
3
+
4
+ module CutePrint
5
+ # @api private
6
+ class LocationLabel
7
+
8
+ FORMATS = {
9
+ filename: Filename,
10
+ path: Path,
11
+ }
12
+ private_constant :FORMATS
13
+
14
+ def self.make(format, location)
15
+ klass = FORMATS[format]
16
+ unless klass
17
+ raise ArgumentError, "Unknown location format: #{format.inspect}"
18
+ end
19
+ klass.new(location)
20
+ end
21
+
22
+ end
23
+ end
@@ -1,4 +1,3 @@
1
- require_relative "default_printer"
2
1
  require_relative "printer"
3
2
 
4
3
  module CutePrint
@@ -9,24 +8,24 @@ module CutePrint
9
8
  # the module itself is not.
10
9
  module Mixin
11
10
 
12
- # @see Printer#q
11
+ # @see CutePrint#q
13
12
  def q(*args, &block)
14
- CutePrint::DefaultPrinter.printer.q(*args, &block)
13
+ CutePrint.q(*args, &block)
15
14
  end
16
15
 
17
- # @see Printer#ql
16
+ # @see CutePrint#ql
18
17
  def ql(*args, &block)
19
- CutePrint::DefaultPrinter.printer.ql(*args, &block)
18
+ CutePrint.ql(*args, &block)
20
19
  end
21
20
 
22
- # @see Printer#qq
21
+ # @see CutePrint#qq
23
22
  def qq(*args, &block)
24
- CutePrint::DefaultPrinter.printer.qq(*args, &block)
23
+ CutePrint.qq(*args, &block)
25
24
  end
26
25
 
27
- # @see Printer#qql
26
+ # @see CutePrint#qql
28
27
  def qql(*args, &block)
29
- CutePrint::DefaultPrinter.printer.qql(*args, &block)
28
+ CutePrint.qql(*args, &block)
30
29
  end
31
30
 
32
31
  # Debug a call chain by printing self and then returning self.
@@ -36,7 +35,7 @@ module CutePrint
36
35
  self
37
36
  end
38
37
 
39
- # Debug a call chain by printing self, with source position, and
38
+ # Debug a call chain by printing self, with source location, and
40
39
  # then returning self.
41
40
  # @return [Object] self
42
41
  def tapql
@@ -46,13 +45,15 @@ module CutePrint
46
45
 
47
46
  # Debug a call chain by pretty-printing self and then returning
48
47
  # self.
48
+ # @return [Object] self
49
49
  def tapqq
50
50
  qq self
51
51
  self
52
52
  end
53
53
 
54
- # Debug a call chain by printing the source location,
55
- # pretty-printing self, and then returning self.
54
+ # Debug a call chain by pretty-printing self, with source
55
+ # location, and then returning self.
56
+ # @return [Object] self
56
57
  def tapqql
57
58
  qql self
58
59
  self
@@ -0,0 +1,38 @@
1
+ module CutePrint
2
+ # @api private
3
+ class OutlineLabeler
4
+
5
+ def self.label(formatter, width, label, value)
6
+ new(formatter, width, label, value).labeled
7
+ end
8
+
9
+ def initialize(formatter, width, label, value)
10
+ @formatter = formatter
11
+ @width = width
12
+ @label = label
13
+ @value = value
14
+ end
15
+
16
+ def labeled
17
+ [labeled_first_line] + indented_remaining_lines
18
+ end
19
+
20
+ private
21
+
22
+ def labeled_first_line
23
+ @label + lines.first
24
+ end
25
+
26
+ def indented_remaining_lines
27
+ indent = ' ' * @label.size
28
+ lines.to_a[1..-1].map do |line|
29
+ indent + line
30
+ end
31
+ end
32
+
33
+ def lines
34
+ @lines ||= @formatter.format(@width - @label.size, @value)
35
+ end
36
+
37
+ end
38
+ end
@@ -36,15 +36,7 @@ module CutePrint
36
36
  @location_format = :filename
37
37
  end
38
38
 
39
- # Inspect and write one or more objects.
40
- #
41
- # If called without a block, prints the inspected arguments, one
42
- # on a line.
43
- #
44
- # If called with a block, prints the source code of the block and
45
- # the inspected result of the block.
46
- #
47
- # @return nil
39
+ # @see CutePrint.q
48
40
  def q(*values, &block)
49
41
  formatter = Formatter.new(
50
42
  method: __method__,
@@ -57,15 +49,7 @@ module CutePrint
57
49
  nil
58
50
  end
59
51
 
60
- # Inspect and write one or more objects, with source location.
61
- #
62
- # If called without a block, prints the inspected arguments, one
63
- # on a line.
64
- #
65
- # If called with a block, prints the source code of the block and
66
- # the inspected result of the block.
67
- #
68
- # @return nil
52
+ # @see CutePrint.ql
69
53
  def ql(*values, &block)
70
54
  formatter = Formatter.new(
71
55
  method: __method__,
@@ -78,15 +62,7 @@ module CutePrint
78
62
  nil
79
63
  end
80
64
 
81
- # Pretty-print and write one or more objects.
82
- #
83
- # If called without a block, pretty-prints the pretty-printed
84
- # arguments, one on a line.
85
- #
86
- # If called with a block, prints the source code of the block and
87
- # pretty-prints the result of the block.
88
- #
89
- # @return nil
65
+ # @see CutePrint.qq
90
66
  def qq(*values, &block)
91
67
  formatter = Formatter.new(
92
68
  method: __method__,
@@ -98,15 +74,7 @@ module CutePrint
98
74
  nil
99
75
  end
100
76
 
101
- # Pretty-print and write one or more objects, with source location.
102
- #
103
- # If called without a block, pretty-prints the arguments, one on a
104
- # line.
105
- #
106
- # If called with a block, prints the source code of the block and
107
- # pretty-prints the result of the block.
108
- #
109
- # @return nil
77
+ # @see CutePrint.qql
110
78
  def qql(*values, &block)
111
79
  formatter = Formatter.new(
112
80
  method: __method__,
@@ -0,0 +1,26 @@
1
+ require_relative "ruby_parser"
2
+
3
+ module CutePrint
4
+ # @api private
5
+ class SourceLabel
6
+
7
+ def initialize(block, method)
8
+ @block = block
9
+ @method = method
10
+ end
11
+
12
+ def to_s
13
+ "#{block_code} is "
14
+ end
15
+
16
+ private
17
+
18
+ def block_code
19
+ ruby_parser = RubyParser.from_block(@block)
20
+ parsed_code = ruby_parser.parse
21
+ method_call = parsed_code.first_call_to_method(@method)
22
+ method_call.block.to_ruby
23
+ end
24
+
25
+ end
26
+ end
data/lib/cute_print.rb CHANGED
@@ -1,26 +1,2 @@
1
- require_relative "cute_print/configure"
1
+ require_relative "cute_print/core"
2
2
  require_relative "cute_print/core_ext"
3
- require_relative "cute_print/default_printer"
4
-
5
- # Like Kernel#p, only fancier. For example, this code:
6
- #
7
- # require 'cute_print'
8
- # q { 1 + 2 }
9
- #
10
- # prints this to $stderr:
11
- #
12
- # (1 + 2) is 3
13
- module CutePrint
14
-
15
- # Configure the library. For example:
16
- #
17
- # CutePrint.configure do |c|
18
- # c.out = $stdout
19
- # end
20
- #
21
- # @yieldparam config [Configure]
22
- def self.configure(&block)
23
- Configure.new(DefaultPrinter.printer, &block)
24
- end
25
-
26
- end
@@ -0,0 +1,20 @@
1
+ require_relative "../spec_helper"
2
+
3
+ require "cute_print/format/inspect"
4
+
5
+ module CutePrint
6
+ module Format
7
+ describe Inspect do
8
+
9
+ let(:width) { 80 }
10
+ let(:value) { (1..5).to_a }
11
+ subject { Inspect.new.format(width, value) }
12
+ specify do
13
+ expect(subject).to eq [
14
+ "[1, 2, 3, 4, 5]\n",
15
+ ]
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ require_relative "../spec_helper"
2
+
3
+ require "cute_print/format/pretty_print"
4
+
5
+ module CutePrint
6
+ module Format
7
+ describe PrettyPrint do
8
+
9
+ let(:value) { (1..5).to_a }
10
+ subject { PrettyPrint.new.format(width, value).to_a }
11
+
12
+ context "fits on one line" do
13
+ let(:width) { 80 }
14
+ specify do
15
+ expect(subject).to eq [
16
+ "[1, 2, 3, 4, 5]\n",
17
+ ]
18
+ end
19
+ end
20
+
21
+ context "needs multiple lines" do
22
+ let(:width) { 5 }
23
+ specify do
24
+ expect(subject).to eq [
25
+ "[1,\n",
26
+ " 2,\n",
27
+ " 3,\n",
28
+ " 4,\n",
29
+ " 5]\n",
30
+ ]
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "spec_helper"
2
+
3
+ require "cute_print/inline_labeler"
4
+
5
+ module CutePrint
6
+ describe InlineLabeler do
7
+
8
+ let(:label) { "foo.rb:1: " }
9
+ let(:value) { [1, 2, 3, 4, 5] }
10
+ subject { InlineLabeler.label(formatter, width, label, value) }
11
+
12
+ context "single line" do
13
+ let(:formatter) { Format::Inspect.new }
14
+ let(:width) { 80 }
15
+ specify do
16
+ expect(subject).to eq [
17
+ "foo.rb:1:\n",
18
+ " [1, 2, 3, 4, 5]\n",
19
+ ]
20
+ end
21
+ end
22
+
23
+ context "multiple lines" do
24
+ let(:formatter) { Format::PrettyPrint.new }
25
+ let(:width) { 4 }
26
+ specify do
27
+ expect(subject).to eq [
28
+ "foo.rb:1:\n",
29
+ " [1,\n",
30
+ " 2,\n",
31
+ " 3,\n",
32
+ " 4,\n",
33
+ " 5]\n",
34
+ ]
35
+ end
36
+ end
37
+
38
+ end
39
+ end