marktable 0.0.3 → 0.0.5

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: 5511339f01122f8adb6afe93a836eb2f5278786ab11d289682eca258f044567c
4
- data.tar.gz: bb4ea8e3fd5e9bdbdf4b119c77da6907e225d4bdcae91281c3aab0514a665b7a
3
+ metadata.gz: a3e8c1a76daf4c3d03f628200baabad28e31008e444522fb2d6c7337f623bdd8
4
+ data.tar.gz: c83d7b0a83b21c2c292d5a76963f626ddeae5bebca42363f227f6bf65fed6dc2
5
5
  SHA512:
6
- metadata.gz: 95d08a37b3e158597fc8c7b8d492aa618437010a4c72381b166952ba7fca5aeb9b4d55f993da2caec41524e1122fe162cb6cb2c2ba7cae5c818aa6038c2ae832
7
- data.tar.gz: 55ccc61117f6e9d11097996716d76b0825f6e07329610e6b7fc6f063261cc0b1a482a5af92f787a830568c1533de02e5135bef97c448b65a7390dbb142b002cc
6
+ metadata.gz: f0f766d34db37402b6d4cbb52e37a1e33172175c5ba622051ce9899ded65de3c9fa045f7d21a234c9fc03856bfaefcf7b9b8d5f1fb346bd0c47efbee87e86236
7
+ data.tar.gz: 1a34281c031a0c47da3ff074f6206304fc569663a04ffab9246bd12a137eb7fd53a6745415991d6530d384f67e05eca480db02bdd9c49122687a9ae820475037
@@ -1,75 +1,155 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara'
4
+ require 'nokogiri'
5
+
3
6
  RSpec::Matchers.define :match_markdown do |expected_markdown|
4
7
  match do |actual|
5
- # Handle markdown string, array of hashes, and Marktable::Table objects
6
- actual_data = case actual
7
- when String
8
- Marktable.parse(actual)
9
- when Marktable::Table
10
- actual.to_a
11
- else
12
- actual
13
- end
14
-
15
- expected_data = Marktable.parse(expected_markdown)
16
-
17
- # Normalize data by trimming whitespace in cell values
18
- normalize = ->(data) {
19
- data.map do |row|
20
- if row.is_a?(Hash)
21
- row.transform_values { |v| v.to_s.strip }
22
- else
23
- row.map { |v| v.to_s.strip }
24
- end
25
- end
26
- }
27
-
28
- actual_data = normalize.call(actual_data)
29
- expected_data = normalize.call(expected_data)
8
+ @actual_data = parse_input(actual)
9
+ @expected_data = parse_input(expected_markdown)
30
10
 
31
- # Compare the parsed data structures
32
- actual_data == expected_data
11
+ normalize(@actual_data) == normalize(@expected_data)
33
12
  end
34
13
 
35
14
  failure_message do |actual|
36
- # Parse data for comparison output
37
- actual_data = case actual
38
- when String
39
- Marktable.parse(actual)
40
- when Marktable::Table
41
- actual.to_a
42
- else
43
- actual
44
- end
45
- expected_data = Marktable.parse(expected_markdown)
15
+ @actual_data = parse_input(actual)
16
+ @expected_data = parse_input(expected_markdown)
46
17
 
47
- # Format both tables properly for display
48
- actual_formatted = Marktable.table(actual_data).to_s
49
- expected_formatted = Marktable.table(expected_data).to_s
18
+ format_failure_message(@expected_data, @actual_data)
19
+ end
50
20
 
21
+ failure_message_when_negated do |actual|
22
+ @actual_data = parse_input(actual)
23
+
24
+ "Expected markdown tables to differ, but they match:\n\n" \
25
+ "#{format_as_markdown(@actual_data)}"
26
+ end
27
+
28
+ private
29
+
30
+ # Parse different types of inputs into a common data structure
31
+ def parse_input(input)
32
+ case input
33
+ when String
34
+ if looks_like_html?(input)
35
+ parse_html_table(input)
36
+ else
37
+ Marktable.parse(input)
38
+ end
39
+ when Marktable::Table
40
+ input.to_a
41
+ when Capybara::Node::Element
42
+ parse_capybara_element(input)
43
+ else
44
+ input
45
+ end
46
+ end
47
+
48
+ def looks_like_html?(text)
49
+ text.include?('<table') || text.include?('<tr') || text.include?('<td')
50
+ end
51
+
52
+ # Normalize data by trimming whitespace in cell values
53
+ def normalize(data)
54
+ data.map do |row|
55
+ if row.is_a?(Hash)
56
+ row.transform_values { |v| v.to_s.strip }
57
+ else
58
+ row.map { |v| v.to_s.strip }
59
+ end
60
+ end
61
+ end
62
+
63
+ def format_failure_message(expected_data, actual_data)
64
+ expected_formatted = format_as_markdown(expected_data)
65
+ actual_formatted = format_as_markdown(actual_data)
66
+
51
67
  "Expected markdown table to match:\n\n" \
52
68
  "Expected:\n#{expected_formatted}\n\n" \
53
69
  "Actual:\n#{actual_formatted}\n\n" \
54
70
  "Parsed expected data: #{expected_data.inspect}\n" \
55
71
  "Parsed actual data: #{actual_data.inspect}"
56
72
  end
57
-
58
- failure_message_when_negated do |actual|
59
- # Parse data for comparison output
60
- actual_data = case actual
61
- when String
62
- Marktable.parse(actual)
63
- when Marktable::Table
64
- actual.to_a
65
- else
66
- actual
67
- end
73
+
74
+ def format_as_markdown(data)
75
+ Marktable.table(data).to_s
76
+ end
77
+
78
+ # Parse HTML table into rows of data using Nokogiri
79
+ def parse_html_table(html)
80
+ doc = Nokogiri::HTML(html)
68
81
 
69
- # Generate properly formatted markdown for display
70
- actual_formatted = Marktable.table(actual_data).to_s
71
-
72
- "Expected markdown tables to differ, but they match:\n\n" \
73
- "#{actual_formatted}"
82
+ # Extract headers
83
+ headers = extract_headers_with_nokogiri(doc)
84
+
85
+ # Extract body rows
86
+ body_rows = extract_body_rows_with_nokogiri(doc)
87
+
88
+ # Convert rows to hashes using the headers
89
+ body_rows.map do |row|
90
+ row_to_hash(row, headers)
91
+ end
92
+ end
93
+
94
+ def extract_headers_with_nokogiri(doc)
95
+ headers = doc.css('thead th, thead td').map(&:text)
96
+ if headers.empty? && doc.css('tr').any?
97
+ headers = doc.css('tr:first-child th, tr:first-child td').map(&:text)
98
+ end
99
+ headers
100
+ end
101
+
102
+ def extract_body_rows_with_nokogiri(doc)
103
+ tbody_rows = doc.css('tbody tr').map { |tr| tr.css('th, td').map(&:text) }
104
+
105
+ # If no tbody, use all rows after the first (assuming first is header)
106
+ if tbody_rows.empty?
107
+ tbody_rows = doc.css('tr')[1..-1].to_a.map { |tr| tr.css('th, td').map(&:text) }
108
+ end
109
+
110
+ tbody_rows
111
+ end
112
+
113
+ def row_to_hash(cells, headers)
114
+ row_hash = {}
115
+ headers.each_with_index do |header, i|
116
+ row_hash[header] = i < cells.length ? cells[i] : ''
117
+ end
118
+ row_hash
119
+ end
120
+
121
+ def parse_capybara_element(element)
122
+ # Extract headers
123
+ headers = extract_headers_from_capybara(element)
124
+
125
+ # Extract body rows
126
+ body_rows = extract_body_rows_from_capybara(element)
127
+
128
+ # Convert rows to hashes using the headers
129
+ body_rows.map do |cells|
130
+ row_to_hash(cells, headers)
131
+ end
132
+ end
133
+
134
+ def extract_headers_from_capybara(element)
135
+ thead = element.first('thead') rescue nil
136
+ if thead
137
+ thead.all('th, td').map(&:text)
138
+ else
139
+ first_row = element.first('tr')
140
+ first_row ? first_row.all('th, td').map(&:text) : []
141
+ end
142
+ end
143
+
144
+ def extract_body_rows_from_capybara(element)
145
+ body_rows = element.all('tbody tr')
146
+
147
+ # If no tbody, assume first row is header and skip it
148
+ if body_rows.empty?
149
+ all_rows = element.all('tr')
150
+ body_rows = all_rows[1..]
151
+ end
152
+
153
+ body_rows.map { |tr| tr.all('th, td').map(&:text) }
74
154
  end
75
155
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marktable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francois Gaspard
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: nokogiri
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.14'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.14'
26
40
  description: Provides a row-based object model and utility methods for creating, parsing,
27
41
  transforming, and exporting Markdown tables in Ruby.
28
42
  email: