super_diff 0.1.0 → 0.2.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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -89
  3. data/lib/super_diff.rb +33 -47
  4. data/lib/super_diff/active_record.rb +41 -0
  5. data/lib/super_diff/active_record/diff_formatters.rb +10 -0
  6. data/lib/super_diff/active_record/diff_formatters/active_record_relation.rb +23 -0
  7. data/lib/super_diff/active_record/differs.rb +10 -0
  8. data/lib/super_diff/active_record/differs/active_record_relation.rb +30 -0
  9. data/lib/super_diff/active_record/object_inspection.rb +14 -0
  10. data/lib/super_diff/active_record/object_inspection/inspectors.rb +16 -0
  11. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_model.rb +38 -0
  12. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_relation.rb +18 -0
  13. data/lib/super_diff/active_record/object_inspection/map_extension.rb +18 -0
  14. data/lib/super_diff/active_record/operation_sequences.rb +10 -0
  15. data/lib/super_diff/active_record/operation_sequences/active_record_relation.rb +16 -0
  16. data/lib/super_diff/active_record/operational_sequencers.rb +14 -0
  17. data/lib/super_diff/active_record/operational_sequencers/active_record_model.rb +19 -0
  18. data/lib/super_diff/active_record/operational_sequencers/active_record_relation.rb +24 -0
  19. data/lib/super_diff/active_support.rb +33 -0
  20. data/lib/super_diff/active_support/diff_formatters.rb +10 -0
  21. data/lib/super_diff/active_support/diff_formatters/hash_with_indifferent_access.rb +36 -0
  22. data/lib/super_diff/active_support/differs.rb +10 -0
  23. data/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +36 -0
  24. data/lib/super_diff/active_support/object_inspection.rb +14 -0
  25. data/lib/super_diff/active_support/object_inspection/inspectors.rb +12 -0
  26. data/lib/super_diff/active_support/object_inspection/inspectors/hash_with_indifferent_access.rb +18 -0
  27. data/lib/super_diff/active_support/object_inspection/map_extension.rb +15 -0
  28. data/lib/super_diff/active_support/operation_sequences.rb +10 -0
  29. data/lib/super_diff/active_support/operation_sequences/hash_with_indifferent_access.rb +16 -0
  30. data/lib/super_diff/active_support/operational_sequencers.rb +10 -0
  31. data/lib/super_diff/active_support/operational_sequencers/hash_with_indifferent_access.rb +21 -0
  32. data/lib/super_diff/colorized_document_extensions.rb +17 -0
  33. data/lib/super_diff/csi.rb +45 -15
  34. data/lib/super_diff/csi/bold_sequence.rb +9 -0
  35. data/lib/super_diff/csi/color.rb +62 -0
  36. data/lib/super_diff/csi/color_sequence_block.rb +28 -0
  37. data/lib/super_diff/csi/colorized_document.rb +72 -0
  38. data/lib/super_diff/csi/document.rb +183 -0
  39. data/lib/super_diff/csi/eight_bit_color.rb +72 -26
  40. data/lib/super_diff/csi/four_bit_color.rb +63 -29
  41. data/lib/super_diff/csi/twenty_four_bit_color.rb +79 -18
  42. data/lib/super_diff/csi/uncolorized_document.rb +29 -0
  43. data/lib/super_diff/diff_formatter.rb +10 -15
  44. data/lib/super_diff/diff_formatters.rb +10 -1
  45. data/lib/super_diff/diff_formatters/base.rb +12 -17
  46. data/lib/super_diff/diff_formatters/collection.rb +81 -50
  47. data/lib/super_diff/diff_formatters/{object.rb → custom_object.rb} +12 -9
  48. data/lib/super_diff/diff_formatters/default_object.rb +48 -0
  49. data/lib/super_diff/diff_formatters/multiline_string.rb +31 -0
  50. data/lib/super_diff/differ.rb +35 -32
  51. data/lib/super_diff/differs.rb +16 -1
  52. data/lib/super_diff/differs/array.rb +2 -2
  53. data/lib/super_diff/differs/base.rb +11 -21
  54. data/lib/super_diff/differs/custom_object.rb +26 -0
  55. data/lib/super_diff/differs/default_object.rb +25 -0
  56. data/lib/super_diff/differs/empty.rb +1 -1
  57. data/lib/super_diff/differs/hash.rb +2 -2
  58. data/lib/super_diff/differs/{multi_line_string.rb → multiline_string.rb} +6 -5
  59. data/lib/super_diff/equality_matcher.rb +9 -22
  60. data/lib/super_diff/equality_matchers.rb +19 -1
  61. data/lib/super_diff/equality_matchers/array.rb +6 -4
  62. data/lib/super_diff/equality_matchers/base.rb +8 -16
  63. data/lib/super_diff/equality_matchers/default.rb +60 -0
  64. data/lib/super_diff/equality_matchers/hash.rb +6 -4
  65. data/lib/super_diff/equality_matchers/{multi_line_string.rb → multiline_string.rb} +9 -6
  66. data/lib/super_diff/equality_matchers/primitive.rb +34 -0
  67. data/lib/super_diff/equality_matchers/{single_line_string.rb → singleline_string.rb} +7 -5
  68. data/lib/super_diff/helpers.rb +17 -81
  69. data/lib/super_diff/no_differ_available_error.rb +22 -0
  70. data/lib/super_diff/{errors.rb → no_operational_sequencer_available_error.rb} +0 -0
  71. data/lib/super_diff/object_inspection.rb +24 -0
  72. data/lib/super_diff/object_inspection/inspection_tree.rb +144 -0
  73. data/lib/super_diff/object_inspection/inspector.rb +27 -0
  74. data/lib/super_diff/object_inspection/inspectors.rb +18 -0
  75. data/lib/super_diff/object_inspection/inspectors/array.rb +22 -0
  76. data/lib/super_diff/object_inspection/inspectors/custom_object.rb +27 -0
  77. data/lib/super_diff/object_inspection/inspectors/default_object.rb +47 -0
  78. data/lib/super_diff/object_inspection/inspectors/hash.rb +22 -0
  79. data/lib/super_diff/object_inspection/inspectors/primitive.rb +13 -0
  80. data/lib/super_diff/object_inspection/inspectors/string.rb +13 -0
  81. data/lib/super_diff/object_inspection/map.rb +28 -0
  82. data/lib/super_diff/object_inspection/nodes.rb +49 -0
  83. data/lib/super_diff/object_inspection/nodes/base.rb +86 -0
  84. data/lib/super_diff/object_inspection/nodes/break.rb +15 -0
  85. data/lib/super_diff/object_inspection/nodes/inspection.rb +15 -0
  86. data/lib/super_diff/object_inspection/nodes/nesting.rb +16 -0
  87. data/lib/super_diff/object_inspection/nodes/text.rb +15 -0
  88. data/lib/super_diff/object_inspection/nodes/when_empty.rb +30 -0
  89. data/lib/super_diff/object_inspection/nodes/when_multiline.rb +22 -0
  90. data/lib/super_diff/object_inspection/nodes/when_non_empty.rb +30 -0
  91. data/lib/super_diff/object_inspection/nodes/when_singleline.rb +24 -0
  92. data/lib/super_diff/operation_sequences.rb +9 -0
  93. data/lib/super_diff/operation_sequences/base.rb +1 -1
  94. data/lib/super_diff/operation_sequences/{object.rb → custom_object.rb} +4 -3
  95. data/lib/super_diff/operation_sequences/default_object.rb +25 -0
  96. data/lib/super_diff/operational_sequencer.rb +23 -18
  97. data/lib/super_diff/operational_sequencers.rb +12 -1
  98. data/lib/super_diff/operational_sequencers/array.rb +65 -62
  99. data/lib/super_diff/operational_sequencers/base.rb +18 -26
  100. data/lib/super_diff/operational_sequencers/custom_object.rb +35 -0
  101. data/lib/super_diff/operational_sequencers/{object.rb → default_object.rb} +21 -11
  102. data/lib/super_diff/operational_sequencers/hash.rb +8 -5
  103. data/lib/super_diff/operational_sequencers/{multi_line_string.rb → multiline_string.rb} +11 -6
  104. data/lib/super_diff/operations.rb +6 -0
  105. data/lib/super_diff/operations/binary_operation.rb +14 -34
  106. data/lib/super_diff/operations/unary_operation.rb +11 -2
  107. data/lib/super_diff/rails.rb +1 -0
  108. data/lib/super_diff/recursion_guard.rb +47 -0
  109. data/lib/super_diff/rspec-rails.rb +2 -0
  110. data/lib/super_diff/rspec.rb +52 -8
  111. data/lib/super_diff/rspec/augmented_matcher.rb +98 -0
  112. data/lib/super_diff/rspec/configuration.rb +31 -0
  113. data/lib/super_diff/rspec/differ.rb +60 -16
  114. data/lib/super_diff/rspec/differs.rb +13 -0
  115. data/lib/super_diff/rspec/differs/collection_containing_exactly.rb +23 -0
  116. data/lib/super_diff/rspec/differs/partial_array.rb +22 -0
  117. data/lib/super_diff/rspec/differs/partial_hash.rb +22 -0
  118. data/lib/super_diff/rspec/differs/partial_object.rb +22 -0
  119. data/lib/super_diff/rspec/matcher_text_builders.rb +24 -0
  120. data/lib/super_diff/rspec/matcher_text_builders/base.rb +155 -0
  121. data/lib/super_diff/rspec/matcher_text_builders/be_predicate.rb +78 -0
  122. data/lib/super_diff/rspec/matcher_text_builders/contain_exactly.rb +14 -0
  123. data/lib/super_diff/rspec/matcher_text_builders/match.rb +23 -0
  124. data/lib/super_diff/rspec/matcher_text_builders/raise_error.rb +13 -0
  125. data/lib/super_diff/rspec/matcher_text_builders/respond_to.rb +99 -0
  126. data/lib/super_diff/rspec/matcher_text_template.rb +240 -0
  127. data/lib/super_diff/rspec/monkey_patches.rb +601 -98
  128. data/lib/super_diff/rspec/object_inspection.rb +8 -0
  129. data/lib/super_diff/rspec/object_inspection/inspectors.rb +24 -0
  130. data/lib/super_diff/rspec/object_inspection/inspectors/collection_containing_exactly.rb +19 -0
  131. data/lib/super_diff/rspec/object_inspection/inspectors/partial_array.rb +22 -0
  132. data/lib/super_diff/rspec/object_inspection/inspectors/partial_hash.rb +21 -0
  133. data/lib/super_diff/rspec/object_inspection/inspectors/partial_object.rb +21 -0
  134. data/lib/super_diff/rspec/object_inspection/map_extension.rb +23 -0
  135. data/lib/super_diff/rspec/operational_sequencers.rb +22 -0
  136. data/lib/super_diff/rspec/operational_sequencers/collection_containing_exactly.rb +97 -0
  137. data/lib/super_diff/rspec/operational_sequencers/partial_array.rb +23 -0
  138. data/lib/super_diff/rspec/operational_sequencers/partial_hash.rb +32 -0
  139. data/lib/super_diff/rspec/operational_sequencers/partial_object.rb +64 -0
  140. data/lib/super_diff/version.rb +1 -1
  141. data/spec/examples.txt +328 -46
  142. data/spec/integration/rails/active_record_spec.rb +19 -0
  143. data/spec/integration/rails/hash_with_indifferent_access_spec.rb +19 -0
  144. data/spec/integration/rspec/be_falsey_matcher_spec.rb +53 -0
  145. data/spec/integration/rspec/be_matcher_spec.rb +565 -0
  146. data/spec/integration/rspec/be_nil_matcher_spec.rb +53 -0
  147. data/spec/integration/rspec/be_predicate_matcher_spec.rb +546 -0
  148. data/spec/integration/rspec/be_truthy_matcher_spec.rb +57 -0
  149. data/spec/integration/rspec/contain_exactly_matcher_spec.rb +368 -0
  150. data/spec/integration/rspec/eq_matcher_spec.rb +874 -0
  151. data/spec/integration/rspec/have_attributes_matcher_spec.rb +299 -0
  152. data/spec/integration/rspec/include_matcher_spec.rb +350 -0
  153. data/spec/integration/rspec/match_matcher_spec.rb +1258 -0
  154. data/spec/integration/rspec/raise_error_matcher_spec.rb +350 -0
  155. data/spec/integration/rspec/respond_to_matcher_spec.rb +994 -0
  156. data/spec/integration/rspec/unhandled_errors_spec.rb +94 -0
  157. data/spec/spec_helper.rb +19 -4
  158. data/spec/support/colorizer.rb +9 -0
  159. data/spec/support/command_runner.rb +4 -0
  160. data/spec/support/integration/helpers.rb +179 -0
  161. data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +79 -41
  162. data/spec/support/models/a.rb +11 -0
  163. data/spec/support/models/active_record/person.rb +26 -0
  164. data/spec/support/models/active_record/shipping_address.rb +29 -0
  165. data/spec/support/models/customer.rb +24 -0
  166. data/spec/support/models/empty_class.rb +6 -0
  167. data/spec/support/models/item.rb +10 -0
  168. data/spec/support/models/order.rb +9 -0
  169. data/spec/support/models/person.rb +20 -0
  170. data/spec/support/models/player.rb +33 -0
  171. data/spec/support/models/shipping_address.rb +34 -0
  172. data/spec/support/ruby_versions.rb +7 -0
  173. data/spec/support/shared_examples/active_record.rb +338 -0
  174. data/spec/support/shared_examples/hash_with_indifferent_access.rb +233 -0
  175. data/spec/unit/equality_matcher_spec.rb +579 -171
  176. data/spec/unit/object_inspection_spec.rb +1092 -0
  177. data/spec/unit/rspec/matchers/be_compared_to_spec.rb +23 -0
  178. data/spec/unit/rspec/matchers/be_falsey_spec.rb +9 -0
  179. data/spec/unit/rspec/matchers/be_nil_spec.rb +9 -0
  180. data/spec/unit/rspec/matchers/be_predicate_spec.rb +31 -0
  181. data/spec/unit/rspec/matchers/be_spec.rb +17 -0
  182. data/spec/unit/rspec/matchers/be_truthy_spec.rb +9 -0
  183. data/spec/unit/rspec/matchers/contain_exactly_spec.rb +11 -0
  184. data/spec/unit/rspec/matchers/eq_spec.rb +9 -0
  185. data/spec/unit/rspec/matchers/have_attributes_spec.rb +11 -0
  186. data/spec/unit/rspec/matchers/include_spec.rb +21 -0
  187. data/spec/unit/rspec/matchers/match_spec.rb +9 -0
  188. data/spec/unit/rspec/matchers/raise_error_spec.rb +29 -0
  189. data/spec/unit/rspec/matchers/respond_to_spec.rb +78 -0
  190. data/super_diff.gemspec +4 -2
  191. metadata +231 -34
  192. data/lib/super_diff/csi/color_helper.rb +0 -52
  193. data/lib/super_diff/csi/eight_bit_sequence.rb +0 -27
  194. data/lib/super_diff/csi/four_bit_sequence.rb +0 -24
  195. data/lib/super_diff/csi/sequence.rb +0 -22
  196. data/lib/super_diff/csi/twenty_four_bit_sequence.rb +0 -27
  197. data/lib/super_diff/diff_formatters/multi_line_string.rb +0 -31
  198. data/lib/super_diff/differs/object.rb +0 -68
  199. data/lib/super_diff/equality_matchers/object.rb +0 -18
  200. data/lib/super_diff/value_inspection.rb +0 -11
  201. data/spec/integration/rspec_spec.rb +0 -261
  202. data/spec/support/color_helper.rb +0 -49
  203. data/spec/support/person.rb +0 -23
  204. data/spec/support/person_diff_formatter.rb +0 -15
  205. data/spec/support/person_operation_sequence.rb +0 -14
  206. data/spec/support/person_operational_sequencer.rb +0 -19
@@ -0,0 +1,10 @@
1
+ module SuperDiff
2
+ module ActiveSupport
3
+ module OperationSequences
4
+ autoload(
5
+ :HashWithIndifferentAccess,
6
+ "super_diff/active_support/operation_sequences/hash_with_indifferent_access",
7
+ )
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module SuperDiff
2
+ module ActiveSupport
3
+ module OperationSequences
4
+ class HashWithIndifferentAccess < SuperDiff::OperationSequences::Base
5
+ def to_diff(indent_level:, collection_prefix:, add_comma:)
6
+ DiffFormatters::HashWithIndifferentAccess.call(
7
+ self,
8
+ indent_level: indent_level,
9
+ collection_prefix: collection_prefix,
10
+ add_comma: add_comma,
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module SuperDiff
2
+ module ActiveSupport
3
+ module OperationalSequencers
4
+ autoload(
5
+ :HashWithIndifferentAccess,
6
+ "super_diff/active_support/operational_sequencers/hash_with_indifferent_access",
7
+ )
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module SuperDiff
2
+ module ActiveSupport
3
+ module OperationalSequencers
4
+ class HashWithIndifferentAccess < SuperDiff::OperationalSequencers::Hash
5
+ def initialize(expected:, actual:, **rest)
6
+ super
7
+
8
+ if expected.is_a?(::HashWithIndifferentAccess)
9
+ @expected = expected.to_h
10
+ @actual = actual.transform_keys(&:to_s)
11
+ end
12
+
13
+ if actual.is_a?(::HashWithIndifferentAccess)
14
+ @expected = expected.transform_keys(&:to_s)
15
+ @actual = actual.to_h
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module SuperDiff
2
+ module ColorizedDocumentExtensions
3
+ def self.extended(extendee)
4
+ extendee.singleton_class.class_eval do
5
+ alias_method :normal, :text
6
+ end
7
+ end
8
+
9
+ def alpha(*args, **opts, &block)
10
+ colorize(*args, **opts, fg: SuperDiff::COLORS.fetch(:alpha), &block)
11
+ end
12
+
13
+ def beta(*args, **opts, &block)
14
+ colorize(*args, **opts, fg: SuperDiff::COLORS.fetch(:beta), &block)
15
+ end
16
+ end
17
+ end
@@ -1,29 +1,59 @@
1
- require_relative "csi/reset_sequence"
2
-
3
1
  # Source: <https://en.wikipedia.org/wiki/ANSI_escape_code>
4
2
  module SuperDiff
5
3
  module Csi
4
+ autoload :BoldSequence, "super_diff/csi/bold_sequence"
5
+ autoload :Color, "super_diff/csi/color"
6
+ autoload :ColorSequenceBlock, "super_diff/csi/color_sequence_block"
7
+ autoload :ColorizedDocument, "super_diff/csi/colorized_document"
8
+ autoload :Document, "super_diff/csi/document"
9
+ autoload :EightBitColor, "super_diff/csi/eight_bit_color"
10
+ autoload :FourBitColor, "super_diff/csi/four_bit_color"
11
+ autoload :ResetSequence, "super_diff/csi/reset_sequence"
12
+ autoload :TwentyFourBitColor, "super_diff/csi/twenty_four_bit_color"
13
+ autoload :UncolorizedDocument, "super_diff/csi/uncolorized_document"
14
+
15
+ class << self
16
+ attr_writer :color_enabled
17
+ end
18
+
6
19
  def self.reset_sequence
7
20
  ResetSequence.new
8
21
  end
9
22
 
10
- def self.colorize(text, fg: nil, bg: nil)
11
- parts = []
23
+ def self.color_enabled?
24
+ @color_enabled
25
+ end
12
26
 
13
- if fg
14
- parts << fg.sequence_for(:fg)
27
+ def self.colorize(*args, **opts, &block)
28
+ if color_enabled?
29
+ ColorizedDocument.new(*args, **opts, &block)
30
+ else
31
+ UncolorizedDocument.new(*args, **opts, &block)
15
32
  end
33
+ end
16
34
 
17
- if bg
18
- parts << bg.sequence_for(:bg)
19
- end
35
+ def self.decolorize(text)
36
+ text.gsub(/\e\[\d+(?:;\d+)*m(.+?)\e\[0m/, '\1')
37
+ end
38
+
39
+ def self.inspect_colors_in(text)
40
+ [FourBitColor, EightBitColor, TwentyFourBitColor].
41
+ reduce(text) do |str, klass|
42
+ klass.sub_colorized_areas_in(str) do |area, color|
43
+ color_block = colorize("◼︎", color.to_foreground)
20
44
 
21
- (parts + [text, reset_sequence]).join
45
+ layer_indicator =
46
+ if color.foreground?
47
+ "(fg)"
48
+ else
49
+ "(bg)"
50
+ end
51
+
52
+ "#{color_block} #{layer_indicator} ❮#{area}❯"
53
+ end
54
+ end
22
55
  end
56
+
57
+ self.color_enabled = STDOUT.tty?
23
58
  end
24
59
  end
25
-
26
- require_relative "csi/four_bit_color"
27
- require_relative "csi/eight_bit_color"
28
- require_relative "csi/twenty_four_bit_color"
29
- require_relative "csi/color_helper"
@@ -0,0 +1,9 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class BoldSequence
4
+ def to_s
5
+ "\e[1m"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class Color
4
+ def self.exists?(name)
5
+ FourBitColor.exists?(name)
6
+ end
7
+
8
+ def self.resolve(value, layer:)
9
+ if value.is_a?(Symbol)
10
+ FourBitColor.new(value, layer: layer)
11
+ else
12
+ TwentyFourBitColor.new(value, layer: layer)
13
+ end
14
+ end
15
+
16
+ def self.sub_colorized_areas_in(text)
17
+ regex = /(#{opening_regex.source.gsub(/\((.+?)\)/, '\1')})(.+?)\e\[0m/
18
+
19
+ text.gsub(regex) do
20
+ match = Regexp.last_match
21
+
22
+ if match[1] == "\e[0m"
23
+ match[0]
24
+ else
25
+ yield match[2], new(match[1])
26
+ end
27
+ end
28
+ end
29
+
30
+ def to_s
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def foreground?
35
+ layer == :foreground
36
+ end
37
+
38
+ def background?
39
+ layer == :background
40
+ end
41
+
42
+ def to_foreground
43
+ raise NotImplementedError
44
+ end
45
+
46
+ protected
47
+
48
+ attr_reader :layer
49
+
50
+ def interpret_layer!(layer)
51
+ if [:foreground, :background].include?(layer)
52
+ layer
53
+ else
54
+ raise ArgumentError.new(
55
+ "Invalid layer #{layer.inspect}. " +
56
+ "Layer must be :foreground or :background.",
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class ColorSequenceBlock
4
+ include Enumerable
5
+
6
+ def initialize(colors = [])
7
+ @colors = colors
8
+ end
9
+
10
+ def each(&block)
11
+ colors.each(&block)
12
+ end
13
+
14
+ def push(color)
15
+ colors.push(color)
16
+ end
17
+ alias_method :<<, :push
18
+
19
+ def to_s
20
+ colors.map(&:to_s).join
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :colors
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class ColorizedDocument < Document
4
+ def initialize(&block)
5
+ @color_sequences_open_in_parent = []
6
+ super
7
+ end
8
+
9
+ protected
10
+
11
+ def colorize_block(colors, opts, &block)
12
+ color_sequence = build_initial_color_sequence_from(colors, opts)
13
+
14
+ if color_sequences_open_in_parent.any?
15
+ add_part(Csi.reset_sequence)
16
+ end
17
+
18
+ add_part(color_sequence)
19
+ color_sequences_open_in_parent << color_sequence
20
+ evaluate_block(&block)
21
+ add_part(Csi.reset_sequence)
22
+
23
+ color_sequence_to_reopen = color_sequences_open_in_parent.pop
24
+ if color_sequences_open_in_parent.any?
25
+ add_part(color_sequence_to_reopen)
26
+ end
27
+ end
28
+
29
+ def colorize_inline(contents, colors, opts)
30
+ color_sequence = build_initial_color_sequence_from(colors, opts)
31
+
32
+ add_part(color_sequence)
33
+
34
+ contents.each do |content|
35
+ if content.is_a?(self.class)
36
+ content.each do |part|
37
+ if part.is_a?(ColorSequenceBlock)
38
+ add_part(Csi.reset_sequence)
39
+ end
40
+
41
+ add_part(part)
42
+
43
+ if part.is_a?(ResetSequence)
44
+ add_part(color_sequence)
45
+ end
46
+ end
47
+ else
48
+ add_part(content)
49
+ end
50
+ end
51
+
52
+ add_part(Csi.reset_sequence)
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :color_sequences_open_in_parent
58
+
59
+ def build_initial_color_sequence_from(colors, opts)
60
+ ColorSequenceBlock.new(colors).tap do |sequence|
61
+ if opts[:fg]
62
+ sequence << Color.resolve(opts[:fg], layer: :foreground)
63
+ end
64
+
65
+ if opts[:bg]
66
+ sequence << Color.resolve(opts[:bg], layer: :background)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,183 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class Document
4
+ include Enumerable
5
+
6
+ def initialize(&block)
7
+ @parts = []
8
+ @indentation_stack = []
9
+
10
+ if block
11
+ evaluate_block(&block)
12
+ end
13
+ end
14
+
15
+ def each(&block)
16
+ parts.each(&block)
17
+ end
18
+
19
+ def bold(*args, **opts, &block)
20
+ colorize(BoldSequence.new, *args, **opts, &block)
21
+ end
22
+
23
+ def colorize(*args, **opts, &block)
24
+ contents, colors = args.partition do |arg|
25
+ arg.is_a?(String) || arg.is_a?(self.class)
26
+ end
27
+
28
+ if colors[0].is_a?(Symbol)
29
+ if colors[0] == :colorize
30
+ raise ArgumentError, "#colorize can't call itself!"
31
+ else
32
+ public_send(colors[0], *contents, *colors[1..-1], **opts, &block)
33
+ end
34
+ elsif !block && colors.empty? && opts.empty?
35
+ text(*contents)
36
+ elsif block
37
+ colorize_block(colors, opts, &block)
38
+ elsif contents.any?
39
+ colorize_inline(contents, colors, opts)
40
+ else
41
+ raise ArgumentError, "Must have something to colorize!"
42
+ end
43
+ end
44
+ alias_method :colored, :colorize
45
+
46
+ def text(*contents, **, &block)
47
+ if block
48
+ evaluate_block(&block)
49
+ elsif contents.any?
50
+ contents.each do |part|
51
+ add_part(part)
52
+ end
53
+ else
54
+ raise ArgumentError.new(
55
+ "Must have something to add to the document!",
56
+ )
57
+ end
58
+ end
59
+ alias_method :plain, :text
60
+
61
+ def line(*contents, indent_by: 0, &block)
62
+ indent(by: indent_by) do
63
+ add_part(indentation_stack.join)
64
+
65
+ if block
66
+ evaluate_block(&block)
67
+ elsif contents.any?
68
+ text(*contents)
69
+ else
70
+ raise ArgumentError.new(
71
+ "Must have something to add to the document!",
72
+ )
73
+ end
74
+ end
75
+
76
+ add_part("\n")
77
+ end
78
+
79
+ def newline
80
+ add_part("\n")
81
+ end
82
+
83
+ def indent(by:, &block)
84
+ # TODO: This won't work if using `text` manually to add lines
85
+ indentation_stack << (by.is_a?(String) ? by : " " * by)
86
+ evaluate_block(&block)
87
+ indentation_stack.pop
88
+ end
89
+
90
+ def method_missing(name, *args, **opts, &block)
91
+ request = derive_request_from(name)
92
+
93
+ if request
94
+ request.resolve(self, args, opts, &block)
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ def respond_to_missing?(name, include_private = false)
101
+ request = derive_request_from(name)
102
+ !request.nil? || super
103
+ end
104
+
105
+ def to_s
106
+ parts.map(&:to_s).join.rstrip
107
+ end
108
+
109
+ protected
110
+
111
+ attr_reader :parts, :indentation_stack
112
+
113
+ def derive_request_from(name)
114
+ match = name.to_s.match(/\A(.+)_line\Z/)
115
+
116
+ if match
117
+ color_name = match[1].to_sym
118
+
119
+ if respond_to?(color_name)
120
+ MethodRequest.new(name: color_name, line: true)
121
+ elsif Csi::Color.exists?(color_name)
122
+ ColorRequest.new(name: color_name, line: true)
123
+ end
124
+ elsif Csi::Color.exists?(name.to_sym)
125
+ ColorRequest.new(name: name.to_sym, line: false)
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ def evaluate_block(&block)
132
+ if block.arity > 0
133
+ block.call(self)
134
+ else
135
+ instance_eval(&block)
136
+ end
137
+ end
138
+
139
+ def add_part(part)
140
+ parts.push(part)
141
+ end
142
+
143
+ class Request
144
+ def initialize(name:, line:)
145
+ @name = name
146
+ @line = line
147
+ end
148
+
149
+ protected
150
+
151
+ attr_reader :name
152
+
153
+ def for_line?
154
+ @line
155
+ end
156
+
157
+ def wrapper
158
+ if for_line?
159
+ :line
160
+ else
161
+ :text
162
+ end
163
+ end
164
+ end
165
+
166
+ class MethodRequest < Request
167
+ def resolve(doc, args, opts, &block)
168
+ doc.public_send(wrapper) do |d|
169
+ d.public_send(name, *args, **opts, &block)
170
+ end
171
+ end
172
+ end
173
+
174
+ class ColorRequest < Request
175
+ def resolve(doc, args, opts, &block)
176
+ doc.public_send(wrapper) do |d|
177
+ d.colorize(*args, **opts, fg: name, &block)
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end