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 +4 -4
- data/spec/support/matchers/markdown_matchers.rb +136 -56
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3e8c1a76daf4c3d03f628200baabad28e31008e444522fb2d6c7337f623bdd8
|
4
|
+
data.tar.gz: c83d7b0a83b21c2c292d5a76963f626ddeae5bebca42363f227f6bf65fed6dc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
6
|
-
|
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
|
-
|
32
|
-
actual_data == expected_data
|
11
|
+
normalize(@actual_data) == normalize(@expected_data)
|
33
12
|
end
|
34
13
|
|
35
14
|
failure_message do |actual|
|
36
|
-
|
37
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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.
|
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:
|