normalized_match 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8557ff5b699123c45a96ea1cf0f01f5600d0a4eb545861b6d30e64c41a07288
4
- data.tar.gz: 3fd9d9f8754a5f76073709f9666f35423bca7a49ccc4d87c7672102d126bb169
3
+ metadata.gz: 4c9e104e74805c7efb7da5fcc8c15f18ee585d5c9b426eeef35273ba9c90a089
4
+ data.tar.gz: f124fb69c3f63b57d73e3c8517baad43e473cb67db12e831cda4cfc73efa6e00
5
5
  SHA512:
6
- metadata.gz: 0302d4db823ceb4f60c5e3949f25bdd67f3673539d4da52b54d6e1cbf0a3c28e38e61bbc1b99a5d5bde95d8e7da37fe973078454865c82030c981aeba2d84801
7
- data.tar.gz: bf5dec8b78ccc0ce2c574c92ec7663f3367581ca3e86ed8005f414bf618d2b6c35f30aee9a4ff323081048c72442cc5cece22955a5520c6b5c7645e2a7f04b42
6
+ metadata.gz: 56d6051c176e5aad379b460d631c6aa83dd4831ccea7dbbac4ea34787dc1a749e9932ba2559d33299eb532795cbdebcf384a9bb42ea2d6d8f094c6a53f44bf55
7
+ data.tar.gz: ae3cc9960db2e4c515d47843773177412dd9896589e6305ed2c9fe504bc3634e7c887f64c1f13b2f5f5b0f311606d814e5378de3782114ae658a32d9844c9a2e
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Normalized Match
2
2
 
3
+ [![CI](https://github.com/firstdraft/normalized_match/actions/workflows/ci.yml/badge.svg)](https://github.com/firstdraft/normalized_match/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/normalized_match.svg)](https://badge.fury.io/rb/normalized_match)
5
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
6
+
3
7
  A normalized string matcher for RSpec that ignores case, punctuation, and some whitespace differences.
4
8
 
5
9
  ## Installation
@@ -96,7 +100,7 @@ d: 1
96
100
 
97
101
  ## Example Output
98
102
 
99
- When strings don't match after normalization:
103
+ When strings don't match after normalization, the matcher displays a helpful table:
100
104
 
101
105
  ```
102
106
  Normalized match failed!
@@ -6,185 +6,184 @@ module NormalizedMatch
6
6
  COLUMN_PADDING = 4 # 2 spaces on each side
7
7
  LABEL_PADDING = 2 # For borders in label column
8
8
  RSpec::Matchers.define :normalized_match do |expected|
9
- match do |actual|
10
- # Store the original values
11
- @original_expected = expected
12
- @original_actual = actual
9
+ match do |actual|
10
+ # Store the original values
11
+ @original_expected = expected
12
+ @original_actual = actual
13
13
 
14
- # Store normalized versions for cleaner diffs
15
- @expected = normalize_string(expected)
16
- @actual = normalize_string(actual)
14
+ # Store normalized versions for cleaner diffs
15
+ @expected = normalize_string(expected)
16
+ @actual = normalize_string(actual)
17
17
 
18
- # Check if actual contains expected (substring match)
19
- @actual.include?(@expected)
20
- end
21
-
22
- failure_message do |actual|
23
- # Split into lines for side-by-side comparison
24
- orig_expected_lines = @original_expected.to_s.split("\n")
25
- orig_actual_lines = @original_actual.to_s.split("\n")
26
- norm_expected_lines = @expected.to_s.split("\n")
27
- norm_actual_lines = @actual.to_s.split("\n")
28
-
29
- # Check if normalization changed anything
30
- expected_changed = @original_expected.to_s != @expected.to_s
31
- actual_changed = @original_actual.to_s != @actual.to_s
32
- normalization_needed = expected_changed || actual_changed
33
-
34
- # Calculate column widths
35
- max_orig_expected = orig_expected_lines.map(&:length).max || 0
36
- max_orig_actual = orig_actual_lines.map(&:length).max || 0
37
- max_norm_expected = norm_expected_lines.map(&:length).max || 0
38
- max_norm_actual = norm_actual_lines.map(&:length).max || 0
39
-
40
- # Ensure minimum column width for headers
41
- expected_col_width = [max_orig_expected, max_norm_expected, "EXPECTED".length].max + COLUMN_PADDING
42
- actual_col_width = [max_orig_actual, max_norm_actual, "ACTUAL".length].max + COLUMN_PADDING
43
-
44
- # Calculate label column width (for "NORMALIZED" and "ORIGINAL")
45
- label_col_width = ["NORMALIZED".length, "ORIGINAL".length].max + COLUMN_PADDING
46
-
47
- # Build the side-by-side table components
48
-
49
- # Helper to pad and center text
50
- def center_pad(text, width)
51
- text = text.to_s
52
- padding = width - text.length
53
- left_pad = padding / 2
54
- right_pad = padding - left_pad
55
- " " * left_pad + text + " " * right_pad
56
- end
18
+ # Check if actual contains expected (substring match)
19
+ @actual.include?(@expected)
20
+ end
57
21
 
58
- # Helper to left-pad text with 2 spaces on each side
59
- def pad_line(text, width)
60
- " #{text.to_s.ljust(width - COLUMN_PADDING)} "
61
- end
22
+ failure_message do |actual|
23
+ # Split into lines for side-by-side comparison
24
+ orig_expected_lines = @original_expected.to_s.split("\n")
25
+ orig_actual_lines = @original_actual.to_s.split("\n")
26
+ norm_expected_lines = @expected.to_s.split("\n")
27
+ norm_actual_lines = @actual.to_s.split("\n")
28
+
29
+ # Check if normalization changed anything
30
+ expected_changed = @original_expected.to_s != @expected.to_s
31
+ actual_changed = @original_actual.to_s != @actual.to_s
32
+ normalization_needed = expected_changed || actual_changed
33
+
34
+ # Calculate column widths
35
+ max_orig_expected = orig_expected_lines.map(&:length).max || 0
36
+ max_orig_actual = orig_actual_lines.map(&:length).max || 0
37
+ max_norm_expected = norm_expected_lines.map(&:length).max || 0
38
+ max_norm_actual = norm_actual_lines.map(&:length).max || 0
39
+
40
+ # Ensure minimum column width for headers
41
+ expected_col_width = [max_orig_expected, max_norm_expected, "EXPECTED".length].max + COLUMN_PADDING
42
+ actual_col_width = [max_orig_actual, max_norm_actual, "ACTUAL".length].max + COLUMN_PADDING
43
+
44
+ # Calculate label column width (for "NORMALIZED" and "ORIGINAL")
45
+ label_col_width = ["NORMALIZED".length, "ORIGINAL".length].max + COLUMN_PADDING
46
+
47
+ # Build the side-by-side table components
48
+
49
+ # Helper to pad and center text
50
+ def center_pad(text, width)
51
+ text = text.to_s
52
+ padding = width - text.length
53
+ left_pad = padding / 2
54
+ right_pad = padding - left_pad
55
+ " " * left_pad + text + " " * right_pad
56
+ end
62
57
 
63
- # Build header output
64
- header_output = []
65
- header_output << "Normalized match failed!"
66
- header_output << ""
67
-
68
- if normalization_needed
69
- header_output << "To make it easier to match the expected output,"
70
- header_output << "we are \"normalizing\" both the actual output and"
71
- header_output << "expected output in this test. That means we"
72
- header_output << "lowercased, removed punctuation, and compacted"
73
- header_output << "whitespace in both."
74
- header_output << ""
75
- header_output << "But the actual output still doesn't contain the"
76
- header_output << "expected output. Can you spot the difference?"
77
- header_output << ""
78
- else
79
- header_output << "The actual output doesn't contain the expected"
80
- header_output << "output. Can you spot the difference?"
81
- header_output << ""
82
- end
58
+ # Helper to left-pad text with 2 spaces on each side
59
+ def pad_line(text, width)
60
+ " #{text.to_s.ljust(width - COLUMN_PADDING)} "
61
+ end
83
62
 
84
- # Helper to center label text (NORMALIZED/ORIGINAL) in first column
85
- def center_label(label, width, total_rows)
86
- middle_row = total_rows / 2
87
- (0...total_rows).map do |i|
88
- if i == middle_row
89
- label.center(width)
63
+ # Build header output
64
+ header_output = []
65
+ header_output << "Normalized match failed!"
66
+ header_output << ""
67
+
68
+ if normalization_needed
69
+ header_output << "To make it easier to match the expected output,"
70
+ header_output << "we are \"normalizing\" both the actual output and"
71
+ header_output << "expected output in this test. That means we"
72
+ header_output << "lowercased, removed punctuation, and compacted"
73
+ header_output << "whitespace in both."
74
+ header_output << ""
75
+ header_output << "But the actual output still doesn't contain the"
76
+ header_output << "expected output. Can you spot the difference?"
90
77
  else
91
- " " * width
78
+ header_output << "The actual output doesn't contain the expected"
79
+ header_output << "output. Can you spot the difference?"
80
+ end
81
+ header_output << ""
82
+
83
+ # Helper to center label text (NORMALIZED/ORIGINAL) in first column
84
+ def center_label(label, width, total_rows)
85
+ middle_row = total_rows / 2
86
+ (0...total_rows).map do |i|
87
+ if i == middle_row
88
+ label.center(width)
89
+ else
90
+ " " * width
91
+ end
92
+ end
92
93
  end
93
- end
94
- end
95
94
 
96
- # Build the table
97
- table_output = []
95
+ # Build the table
96
+ table_output = []
98
97
 
99
- if normalization_needed
100
- # Build two-section table with labels
101
- # Top border
102
- table_output << "╔" + "═" * label_col_width + "╦" + "═" * expected_col_width + "╦" + "═" * actual_col_width + "╗"
98
+ if normalization_needed
99
+ # Build two-section table with labels
100
+ # Top border
101
+ table_output << "╔" + "═" * label_col_width + "╦" + "═" * expected_col_width + "╦" + "═" * actual_col_width + "╗"
103
102
 
104
- # Header row
105
- table_output << "║" + " " * label_col_width + "║" + center_pad("EXPECTED", expected_col_width) + "║" + center_pad("ACTUAL", actual_col_width) + "║"
103
+ # Header row
104
+ table_output << "║" + " " * label_col_width + "║" + center_pad("EXPECTED", expected_col_width) + "║" + center_pad("ACTUAL", actual_col_width) + "║"
106
105
 
107
- # Header separator
108
- table_output << "╠" + "═" * label_col_width + "╬" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
106
+ # Header separator
107
+ table_output << "╠" + "═" * label_col_width + "╬" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
109
108
 
110
- # NORMALIZED section
111
- max_norm_lines = [norm_expected_lines.length, norm_actual_lines.length].max
112
- normalized_labels = center_label("NORMALIZED", label_col_width - LABEL_PADDING, max_norm_lines)
109
+ # NORMALIZED section
110
+ max_norm_lines = [norm_expected_lines.length, norm_actual_lines.length].max
111
+ normalized_labels = center_label("NORMALIZED", label_col_width - LABEL_PADDING, max_norm_lines)
113
112
 
114
- max_norm_lines.times do |i|
115
- expected_line = norm_expected_lines[i] || ""
116
- actual_line = norm_actual_lines[i] || ""
117
- label = normalized_labels[i]
118
- table_output << "║ #{label} ║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
119
- end
113
+ max_norm_lines.times do |i|
114
+ expected_line = norm_expected_lines[i] || ""
115
+ actual_line = norm_actual_lines[i] || ""
116
+ label = normalized_labels[i]
117
+ table_output << "║ #{label} ║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
118
+ end
120
119
 
121
- # Middle separator
122
- table_output << "╠" + "═" * label_col_width + "╬" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
120
+ # Middle separator
121
+ table_output << "╠" + "═" * label_col_width + "╬" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
123
122
 
124
- # ORIGINAL section
125
- max_lines = [orig_expected_lines.length, orig_actual_lines.length].max
126
- original_labels = center_label("ORIGINAL", label_col_width - LABEL_PADDING, max_lines)
123
+ # ORIGINAL section
124
+ max_lines = [orig_expected_lines.length, orig_actual_lines.length].max
125
+ original_labels = center_label("ORIGINAL", label_col_width - LABEL_PADDING, max_lines)
127
126
 
128
- max_lines.times do |i|
129
- expected_line = orig_expected_lines[i] || ""
130
- actual_line = orig_actual_lines[i] || ""
131
- label = original_labels[i]
132
- table_output << "║ #{label} ║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
133
- end
127
+ max_lines.times do |i|
128
+ expected_line = orig_expected_lines[i] || ""
129
+ actual_line = orig_actual_lines[i] || ""
130
+ label = original_labels[i]
131
+ table_output << "║ #{label} ║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
132
+ end
134
133
 
135
- # Bottom border
136
- table_output << "╚" + "═" * label_col_width + "╩" + "═" * expected_col_width + "╩" + "═" * actual_col_width + "╝"
137
- else
138
- # Build simple table without labels column
139
- # Top border
140
- table_output << "╔" + "═" * expected_col_width + "╦" + "═" * actual_col_width + "╗"
134
+ # Bottom border
135
+ table_output << "╚" + "═" * label_col_width + "╩" + "═" * expected_col_width + "╩" + "═" * actual_col_width + "╝"
136
+ else
137
+ # Build simple table without labels column
138
+ # Top border
139
+ table_output << "╔" + "═" * expected_col_width + "╦" + "═" * actual_col_width + "╗"
141
140
 
142
- # Header row
143
- table_output << "║" + center_pad("EXPECTED", expected_col_width) + "║" + center_pad("ACTUAL", actual_col_width) + "║"
141
+ # Header row
142
+ table_output << "║" + center_pad("EXPECTED", expected_col_width) + "║" + center_pad("ACTUAL", actual_col_width) + "║"
144
143
 
145
- # Header separator
146
- table_output << "╠" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
144
+ # Header separator
145
+ table_output << "╠" + "═" * expected_col_width + "╬" + "═" * actual_col_width + "╣"
147
146
 
148
- # Data rows (using original since no normalization happened)
149
- max_lines = [orig_expected_lines.length, orig_actual_lines.length].max
147
+ # Data rows (using original since no normalization happened)
148
+ max_lines = [orig_expected_lines.length, orig_actual_lines.length].max
150
149
 
151
- max_lines.times do |i|
152
- expected_line = orig_expected_lines[i] || ""
153
- actual_line = orig_actual_lines[i] || ""
154
- table_output << "║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
155
- end
150
+ max_lines.times do |i|
151
+ expected_line = orig_expected_lines[i] || ""
152
+ actual_line = orig_actual_lines[i] || ""
153
+ table_output << "║" + pad_line(expected_line, expected_col_width) + "║" + pad_line(actual_line, actual_col_width) + "║"
154
+ end
156
155
 
157
- # Bottom border
158
- table_output << "╚" + "═" * expected_col_width + "╩" + "═" * actual_col_width + "╝"
159
- end
156
+ # Bottom border
157
+ table_output << "╚" + "═" * expected_col_width + "╩" + "═" * actual_col_width + "╝"
158
+ end
160
159
 
161
- # Combine all parts - header, then table (with extra newline between)
162
- # Add a trailing newline so there's a blank line before RSpec's diff (when it appears)
163
- message = header_output.join("\n") + "\n" + table_output.join("\n") + "\n"
160
+ # Combine all parts - header, then table (with extra newline between)
161
+ # Add a trailing newline so there's a blank line before RSpec's diff (when it appears)
162
+ message = header_output.join("\n") + "\n" + table_output.join("\n") + "\n"
164
163
 
165
- message
166
- end
164
+ message
165
+ end
167
166
 
168
- # These methods make the matcher work better with rspec-enriched_json
169
- attr_reader :expected, :actual
167
+ # These methods make the matcher work better with rspec-enriched_json
168
+ attr_reader :expected, :actual
170
169
 
171
- # Provide access to original values if needed
172
- attr_reader :original_expected, :original_actual
170
+ # Provide access to original values if needed
171
+ attr_reader :original_expected, :original_actual
173
172
 
174
- # Tell RSpec that expected and actual can be diffed
175
- def diffable?
176
- true
177
- end
173
+ # Tell RSpec that expected and actual can be diffed
174
+ def diffable?
175
+ true
176
+ end
178
177
 
179
- def normalize_string(str)
180
- str.to_s
181
- .gsub(/[^a-zA-Z0-9\s]/, "") # Remove punctuation but keep all whitespace
182
- .downcase
183
- .split("\n") # Split by newlines to preserve them
184
- .map { |line| line.gsub(/\s+/, " ").strip } # Normalize spaces within each line
185
- .reject(&:empty?) # Remove empty lines
186
- .join("\n") # Rejoin with newlines
187
- end
178
+ def normalize_string(str)
179
+ str.to_s
180
+ .gsub(/[^a-zA-Z0-9\s]/, "") # Remove punctuation but keep all whitespace
181
+ .downcase
182
+ .split("\n") # Split by newlines to preserve them
183
+ .map { |line| line.gsub(/\s+/, " ").strip } # Normalize spaces within each line
184
+ .reject(&:empty?) # Remove empty lines
185
+ .join("\n") # Rejoin with newlines
186
+ end
188
187
  end
189
188
  end
190
189
  end
@@ -6,10 +6,3 @@ require_relative "normalized_match/normalized_match_matcher"
6
6
  # Main namespace.
7
7
  module NormalizedMatch
8
8
  end
9
-
10
- # Include the matcher in RSpec when this gem is loaded
11
- if defined?(RSpec)
12
- RSpec.configure do |config|
13
- config.include NormalizedMatch::NormalizedMatchMatcher
14
- end
15
- end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "normalized_match"
5
- spec.version = "0.1.0"
5
+ spec.version = "0.2.0"
6
6
  spec.authors = ["Raghu Betina"]
7
7
  spec.email = ["raghu@firstdraft.com"]
8
8
  spec.homepage = "https://github.com/firstdraft/normalized_match"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: normalized_match
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raghu Betina