html-to-markdown 2.29.0-arm64-darwin

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.
@@ -0,0 +1,24 @@
1
+ module HtmlToMarkdown
2
+ module CLI
3
+ # Module method (module_function creates both module and instance methods)
4
+ #
5
+ # Run the CLI with the given arguments
6
+ #
7
+ # @param argv Command-line arguments (defaults to ARGV)
8
+ # @param stdout Output stream for standard output
9
+ # @param stderr Output stream for standard error
10
+ # @return Exit code (0 for success, non-zero for failure)
11
+ def self.run: (
12
+ ?Array[String] argv,
13
+ ?stdout: IO,
14
+ ?stderr: IO
15
+ ) -> Integer
16
+
17
+ # Instance method version (created by module_function)
18
+ def run: (
19
+ ?Array[String] argv,
20
+ ?stdout: IO,
21
+ ?stderr: IO
22
+ ) -> Integer
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ module HtmlToMarkdown
2
+ module CLIProxy
3
+ # Base error class
4
+ class Error < StandardError
5
+ end
6
+
7
+ # Error when CLI binary is not found
8
+ class MissingBinaryError < Error
9
+ end
10
+
11
+ # Error when CLI execution fails
12
+ class CLIExecutionError < Error
13
+ attr_reader stderr: String
14
+ attr_reader status: Integer?
15
+
16
+ def initialize: (String message, stderr: String, status: Integer?) -> void
17
+ end
18
+
19
+ # Module methods (module_function creates both module and instance methods)
20
+
21
+ # Execute CLI with given arguments
22
+ def self.call: (Array[String] argv) -> String
23
+
24
+ # Find the CLI binary in search paths
25
+ def self.find_cli_binary: () -> Pathname
26
+
27
+ # Get root path of the gem
28
+ def self.root_path: () -> Pathname
29
+
30
+ # Get lib path of the gem
31
+ def self.lib_path: () -> Pathname
32
+
33
+ # Get search paths for CLI binary
34
+ def self.search_paths: (String binary_name) -> Array[Pathname]
35
+
36
+ # Get error message for missing binary
37
+ def self.missing_binary_message: () -> String
38
+
39
+ # Instance method versions (created by module_function)
40
+
41
+ def call: (Array[String] argv) -> String
42
+ def find_cli_binary: () -> Pathname
43
+ def root_path: () -> Pathname
44
+ def lib_path: () -> Pathname
45
+ def search_paths: (String binary_name) -> Array[Pathname]
46
+ def missing_binary_message: () -> String
47
+ end
48
+ end
@@ -0,0 +1,498 @@
1
+ # Type definitions for HtmlToMarkdown Ruby gem
2
+ module HtmlToMarkdown
3
+ VERSION: String
4
+
5
+ # Opaque handle for reusable conversion options
6
+ class Options
7
+ end
8
+
9
+ # Visitor context information passed to visitor callbacks
10
+ class NodeContext
11
+ attr_reader node_type: Symbol
12
+ attr_reader tag_name: String
13
+ attr_reader attributes: Hash[String, String]
14
+ attr_reader depth: Integer
15
+ attr_reader index_in_parent: Integer
16
+ attr_reader parent_tag: String | nil
17
+ attr_reader is_inline: bool
18
+ end
19
+
20
+ # Result of a visitor callback
21
+ type visitor_result = {
22
+ type: :continue,
23
+ } | {
24
+ type: :custom,
25
+ output: String,
26
+ } | {
27
+ type: :skip,
28
+ } | {
29
+ type: :preserve_html,
30
+ } | {
31
+ type: :error,
32
+ message: String,
33
+ }
34
+
35
+ type heading_style = :underlined | :atx | :atx_closed
36
+ type list_indent_type = :spaces | :tabs
37
+ type highlight_style = :double_equal | :html | :bold | :none
38
+ type whitespace_mode = :normalized | :strict
39
+ type newline_style = :spaces | :backslash
40
+ type code_block_style = :indented | :backticks | :tildes
41
+ type output_format = :markdown | :djot
42
+ type preprocessing_preset = :minimal | :standard | :aggressive
43
+
44
+ type preprocessing_options = {
45
+ enabled?: bool,
46
+ preset?: preprocessing_preset,
47
+ remove_navigation?: bool,
48
+ remove_forms?: bool
49
+ }
50
+
51
+ type conversion_options = {
52
+ heading_style?: heading_style,
53
+ list_indent_type?: list_indent_type,
54
+ list_indent_width?: Integer,
55
+ bullets?: String,
56
+ strong_em_symbol?: String,
57
+ escape_asterisks?: bool,
58
+ escape_underscores?: bool,
59
+ escape_misc?: bool,
60
+ escape_ascii?: bool,
61
+ code_language?: String,
62
+ autolinks?: bool,
63
+ default_title?: bool,
64
+ br_in_tables?: bool,
65
+ hocr_spatial_tables?: bool,
66
+ highlight_style?: highlight_style,
67
+ extract_metadata?: bool,
68
+ whitespace_mode?: whitespace_mode,
69
+ strip_newlines?: bool,
70
+ wrap?: bool,
71
+ wrap_width?: Integer,
72
+ convert_as_inline?: bool,
73
+ sub_symbol?: String,
74
+ sup_symbol?: String,
75
+ newline_style?: newline_style,
76
+ code_block_style?: code_block_style,
77
+ keep_inline_images_in?: Array[String],
78
+ preprocessing?: preprocessing_options,
79
+ encoding?: String,
80
+ debug?: bool,
81
+ strip_tags?: Array[String],
82
+ preserve_tags?: Array[String],
83
+ output_format?: output_format,
84
+ skip_images?: bool
85
+ }
86
+
87
+ type inline_image_config = {
88
+ max_decoded_size_bytes?: Integer,
89
+ filename_prefix?: String?,
90
+ capture_svg?: bool,
91
+ infer_dimensions?: bool
92
+ }
93
+
94
+ type inline_image_format = "png" | "jpeg" | "gif" | "bmp" | "webp" | "svg" | String
95
+
96
+ type inline_image_source = "img_data_uri" | "svg_element"
97
+
98
+ type inline_image = {
99
+ data: String,
100
+ format: inline_image_format,
101
+ filename: String?,
102
+ description: String?,
103
+ dimensions: [Integer, Integer]?,
104
+ source: inline_image_source,
105
+ attributes: Hash[String, String]
106
+ }
107
+
108
+ type inline_image_warning = {
109
+ index: Integer,
110
+ message: String
111
+ }
112
+
113
+ type html_extraction = {
114
+ markdown: String,
115
+ inline_images: Array[inline_image],
116
+ warnings: Array[inline_image_warning]
117
+ }
118
+
119
+ type metadata_config = {
120
+ extract_document?: bool,
121
+ extract_headers?: bool,
122
+ extract_links?: bool,
123
+ extract_images?: bool,
124
+ extract_structured_data?: bool,
125
+ max_structured_data_size?: Integer
126
+ }
127
+
128
+ type text_direction = "ltr" | "rtl" | "auto" | nil
129
+
130
+ type document_metadata = {
131
+ title: String?,
132
+ description: String?,
133
+ keywords: Array[String],
134
+ author: String?,
135
+ canonical_url: String?,
136
+ base_href: String?,
137
+ language: String?,
138
+ text_direction: text_direction,
139
+ open_graph: Hash[String, String],
140
+ twitter_card: Hash[String, String],
141
+ meta_tags: Hash[String, String]
142
+ }
143
+
144
+ type header_metadata = {
145
+ level: Integer,
146
+ text: String,
147
+ id: String?,
148
+ depth: Integer,
149
+ html_offset: Integer
150
+ }
151
+
152
+ type link_type = "anchor" | "internal" | "external" | "email" | "phone" | "other"
153
+
154
+ type link_metadata = {
155
+ href: String,
156
+ text: String,
157
+ title: String?,
158
+ link_type: link_type,
159
+ rel: Array[String],
160
+ attributes: Hash[String, String]
161
+ }
162
+
163
+ type image_type = "data_uri" | "inline_svg" | "external" | "relative"
164
+
165
+ type image_metadata = {
166
+ src: String,
167
+ alt: String?,
168
+ title: String?,
169
+ dimensions: [Integer, Integer]?,
170
+ image_type: image_type,
171
+ attributes: Hash[String, String]
172
+ }
173
+
174
+ type structured_data = {
175
+ data_type: "json_ld" | "microdata" | "rdfa",
176
+ raw_json: String,
177
+ schema_type: String?
178
+ }
179
+
180
+ type extended_metadata = {
181
+ document: document_metadata,
182
+ headers: Array[header_metadata],
183
+ links: Array[link_metadata],
184
+ images: Array[image_metadata],
185
+ structured_data: Array[structured_data]
186
+ }
187
+
188
+ type table_data = {
189
+ cells: Array[Array[String]],
190
+ markdown: String,
191
+ is_header_row: Array[bool]
192
+ }
193
+
194
+ type table_extraction_result = {
195
+ content: String,
196
+ metadata: extended_metadata?,
197
+ tables: Array[table_data]
198
+ }
199
+
200
+ # Native methods (implemented in Rust via Magnus/rb-sys)
201
+ # These are aliased from the Rust extension and available as both module and instance methods
202
+ private
203
+
204
+ def self.native_convert: (String html, conversion_options? options) -> String
205
+ def self.native_options: (conversion_options? options_hash) -> Options
206
+ def self.native_convert_with_options: (String html, Options options_handle) -> String
207
+ def self.native_convert_with_inline_images_handle: (
208
+ String html,
209
+ Options options_handle,
210
+ inline_image_config? image_config
211
+ ) -> html_extraction
212
+ def self.native_convert_with_inline_images: (
213
+ String html,
214
+ conversion_options? options,
215
+ inline_image_config? image_config
216
+ ) -> html_extraction
217
+ def self.native_convert_with_metadata_handle: (
218
+ String html,
219
+ Options options_handle,
220
+ metadata_config? metadata_config
221
+ ) -> [String, extended_metadata]
222
+ def self.native_convert_with_metadata: (
223
+ String html,
224
+ conversion_options? options,
225
+ metadata_config? metadata_config
226
+ ) -> [String, extended_metadata]
227
+ def self.native_convert_with_visitor: (
228
+ String html,
229
+ conversion_options? options,
230
+ visitor? visitor
231
+ ) -> String
232
+ def self.native_convert_with_tables: (
233
+ String html,
234
+ conversion_options? options,
235
+ metadata_config? metadata_config
236
+ ) -> table_extraction_result
237
+
238
+ def native_convert: (String html, conversion_options? options) -> String
239
+ def native_options: (conversion_options? options_hash) -> Options
240
+ def native_convert_with_options: (String html, Options options_handle) -> String
241
+ def native_convert_with_inline_images_handle: (
242
+ String html,
243
+ Options options_handle,
244
+ inline_image_config? image_config
245
+ ) -> html_extraction
246
+ def native_convert_with_inline_images: (
247
+ String html,
248
+ conversion_options? options,
249
+ inline_image_config? image_config
250
+ ) -> html_extraction
251
+ def native_convert_with_metadata_handle: (
252
+ String html,
253
+ Options options_handle,
254
+ metadata_config? metadata_config
255
+ ) -> [String, extended_metadata]
256
+ def native_convert_with_metadata: (
257
+ String html,
258
+ conversion_options? options,
259
+ metadata_config? metadata_config
260
+ ) -> [String, extended_metadata]
261
+ def native_convert_with_visitor: (
262
+ String html,
263
+ conversion_options? options,
264
+ visitor? visitor
265
+ ) -> String
266
+ def native_convert_with_tables: (
267
+ String html,
268
+ conversion_options? options,
269
+ metadata_config? metadata_config
270
+ ) -> table_extraction_result
271
+
272
+ # Visitor interface for customizing conversion behavior
273
+ type visitor = Object
274
+
275
+ public
276
+
277
+ # Convert HTML to Markdown with optional configuration and visitor
278
+ #
279
+ # The optional visitor parameter allows customization of conversion behavior for specific elements.
280
+ # When both options and visitor are provided, the visitor can override default conversions.
281
+ #
282
+ # Args:
283
+ # html: HTML string to convert
284
+ # options: Optional conversion configuration
285
+ # visitor: Optional visitor object for customizing conversion
286
+ #
287
+ # Returns:
288
+ # markdown: String - Converted markdown output
289
+ #
290
+ # Example:
291
+ # markdown = HtmlToMarkdown.convert(html, { wrap: true }, my_visitor)
292
+ def self.convert: (String html, ?conversion_options options, ?visitor visitor) -> String
293
+
294
+ # Create a reusable options handle for performance
295
+ def self.options: (?conversion_options options_hash) -> Options
296
+
297
+ # Convert HTML using a pre-built options handle
298
+ def self.convert_with_options: (String html, Options options_handle) -> String
299
+ def self.convert_with_inline_images_handle: (
300
+ String html,
301
+ Options options_handle,
302
+ ?inline_image_config image_config
303
+ ) -> html_extraction
304
+
305
+ # Convert HTML with inline image extraction
306
+ #
307
+ # Optionally accepts a visitor for customizing conversion behavior.
308
+ #
309
+ # Args:
310
+ # html: HTML string to convert
311
+ # options: Optional conversion configuration
312
+ # image_config: Optional inline image extraction configuration
313
+ # visitor: Optional visitor object for customizing conversion
314
+ #
315
+ # Returns:
316
+ # html_extraction: Hash containing markdown, inline_images array, and warnings array
317
+ #
318
+ # Example:
319
+ # result = HtmlToMarkdown.convert_with_inline_images(html, { wrap: true }, image_config, my_visitor)
320
+ def self.convert_with_inline_images: (
321
+ String html,
322
+ ?conversion_options options,
323
+ ?inline_image_config image_config,
324
+ ?visitor visitor
325
+ ) -> html_extraction
326
+
327
+ # Convert HTML to Markdown with a custom visitor (deprecated)
328
+ #
329
+ # DEPRECATED: Use convert() with the optional visitor parameter instead.
330
+ # This method is maintained for backward compatibility.
331
+ #
332
+ # All convert functions now accept optional visitors:
333
+ # - convert(html, options, visitor)
334
+ # - convert_with_inline_images(html, options, image_config, visitor)
335
+ # - convert_with_metadata(html, options, metadata_config, visitor)
336
+ #
337
+ # The visitor object can implement any of the following methods:
338
+ # - visit_element_start(ctx) -> visitor_result
339
+ # - visit_element_end(ctx, output) -> visitor_result
340
+ # - visit_text(ctx, text) -> visitor_result
341
+ # - visit_link(ctx, href, text, title) -> visitor_result
342
+ # - visit_image(ctx, src, alt, title) -> visitor_result
343
+ # - visit_heading(ctx, level, text, id) -> visitor_result
344
+ # - visit_code_block(ctx, lang, code) -> visitor_result
345
+ # - visit_code_inline(ctx, code) -> visitor_result
346
+ # - visit_list_item(ctx, ordered, marker, text) -> visitor_result
347
+ # - visit_list_start(ctx, ordered) -> visitor_result
348
+ # - visit_list_end(ctx, ordered, output) -> visitor_result
349
+ # - visit_table_start(ctx) -> visitor_result
350
+ # - visit_table_row(ctx, cells, is_header) -> visitor_result
351
+ # - visit_table_end(ctx, output) -> visitor_result
352
+ # - visit_blockquote(ctx, content, depth) -> visitor_result
353
+ # - visit_strong(ctx, text) -> visitor_result
354
+ # - visit_emphasis(ctx, text) -> visitor_result
355
+ # - visit_strikethrough(ctx, text) -> visitor_result
356
+ # - visit_underline(ctx, text) -> visitor_result
357
+ # - visit_subscript(ctx, text) -> visitor_result
358
+ # - visit_superscript(ctx, text) -> visitor_result
359
+ # - visit_mark(ctx, text) -> visitor_result
360
+ # - visit_line_break(ctx) -> visitor_result
361
+ # - visit_horizontal_rule(ctx) -> visitor_result
362
+ # - visit_custom_element(ctx, tag_name, html) -> visitor_result
363
+ # - visit_definition_list_start(ctx) -> visitor_result
364
+ # - visit_definition_term(ctx, text) -> visitor_result
365
+ # - visit_definition_description(ctx, text) -> visitor_result
366
+ # - visit_definition_list_end(ctx, output) -> visitor_result
367
+ # - visit_form(ctx, action, method) -> visitor_result
368
+ # - visit_input(ctx, input_type, name, value) -> visitor_result
369
+ # - visit_button(ctx, text) -> visitor_result
370
+ # - visit_audio(ctx, src) -> visitor_result
371
+ # - visit_video(ctx, src) -> visitor_result
372
+ # - visit_iframe(ctx, src) -> visitor_result
373
+ # - visit_details(ctx, open) -> visitor_result
374
+ # - visit_summary(ctx, text) -> visitor_result
375
+ # - visit_figure_start(ctx) -> visitor_result
376
+ # - visit_figcaption(ctx, text) -> visitor_result
377
+ # - visit_figure_end(ctx, output) -> visitor_result
378
+ #
379
+ # Each method should return a Hash with at least :type key:
380
+ # { type: :continue } - Continue with default behavior
381
+ # { type: :custom, output: "..." } - Replace with custom markdown
382
+ # { type: :skip } - Skip this element entirely
383
+ # { type: :preserve_html } - Keep original HTML
384
+ # { type: :error, message: "..." } - Stop conversion with error
385
+ #
386
+ # Args:
387
+ # html: HTML string to convert
388
+ # options: Optional conversion configuration
389
+ # visitor: Visitor object that responds to visitor callback methods
390
+ #
391
+ # Returns:
392
+ # markdown: String - Converted markdown output
393
+ #
394
+ # Example:
395
+ # class MyVisitor
396
+ # def visit_link(ctx, href, text, title = nil)
397
+ # { type: :custom, output: "[#{text}](#{href})" }
398
+ # end
399
+ # end
400
+ #
401
+ # HtmlToMarkdown.convert_with_visitor(html, visitor: MyVisitor.new)
402
+ def self.convert_with_visitor: (String html, ?conversion_options options, visitor: visitor) -> String
403
+
404
+ # Convert HTML to Markdown with metadata extraction
405
+ #
406
+ # Extracts comprehensive metadata (headers, links, images, structured data) during conversion.
407
+ # Optionally accepts a visitor for customizing conversion behavior.
408
+ #
409
+ # Args:
410
+ # html: HTML string to convert
411
+ # options: Optional conversion configuration
412
+ # metadata_config: Optional metadata extraction configuration
413
+ # visitor: Optional visitor object for customizing conversion
414
+ #
415
+ # Returns:
416
+ # Array containing:
417
+ # - [0] markdown: String - Converted markdown output
418
+ # - [1] metadata: Hash - Extracted metadata with document, headers, links, images, structured_data
419
+ #
420
+ # The metadata hash contains:
421
+ # - document: Document-level metadata (title, description, lang, etc.)
422
+ # - headers: List of header elements with hierarchy
423
+ # - links: List of extracted hyperlinks with classification
424
+ # - images: List of extracted images with metadata
425
+ # - structured_data: List of JSON-LD, Microdata, or RDFa blocks
426
+ #
427
+ # Example:
428
+ # html = '<html lang="en"><head><title>Test</title></head><body><h1>Hello</h1></body></html>'
429
+ # markdown, metadata = HtmlToMarkdown.convert_with_metadata(html)
430
+ # puts "Title: #{metadata['document']['title']}"
431
+ # puts "Headers: #{metadata['headers'].length}"
432
+ #
433
+ # Example with visitor:
434
+ # markdown, metadata = HtmlToMarkdown.convert_with_metadata(html, options, metadata_config, my_visitor)
435
+ def self.convert_with_metadata: (
436
+ String html,
437
+ ?conversion_options options,
438
+ ?metadata_config metadata_config,
439
+ ?visitor visitor
440
+ ) -> [String, extended_metadata]
441
+ def self.convert_with_metadata_handle: (
442
+ String html,
443
+ Options options_handle,
444
+ ?metadata_config metadata_config
445
+ ) -> [String, extended_metadata]
446
+
447
+ # Convert HTML and extract tables as structured data
448
+ #
449
+ # Args:
450
+ # html: HTML string to convert
451
+ # options: Optional conversion configuration
452
+ # metadata_config: Optional metadata extraction configuration
453
+ #
454
+ # Returns:
455
+ # table_extraction_result: Hash containing content, metadata, and tables array
456
+ #
457
+ # Example:
458
+ # result = HtmlToMarkdown.convert_with_tables(html)
459
+ # puts result[:tables].length
460
+ def self.convert_with_tables: (
461
+ String html,
462
+ ?conversion_options options,
463
+ ?metadata_config metadata_config
464
+ ) -> table_extraction_result
465
+
466
+ # Instance method versions (created by module_function)
467
+ def convert: (String html, ?conversion_options options, ?visitor visitor) -> String
468
+ def options: (?conversion_options options_hash) -> Options
469
+ def convert_with_options: (String html, Options options_handle) -> String
470
+ def convert_with_inline_images_handle: (
471
+ String html,
472
+ Options options_handle,
473
+ ?inline_image_config image_config
474
+ ) -> html_extraction
475
+ def convert_with_inline_images: (
476
+ String html,
477
+ ?conversion_options options,
478
+ ?inline_image_config image_config,
479
+ ?visitor visitor
480
+ ) -> html_extraction
481
+ def convert_with_visitor: (String html, ?conversion_options options, visitor: visitor) -> String
482
+ def convert_with_metadata: (
483
+ String html,
484
+ ?conversion_options options,
485
+ ?metadata_config metadata_config,
486
+ ?visitor visitor
487
+ ) -> [String, extended_metadata]
488
+ def convert_with_metadata_handle: (
489
+ String html,
490
+ Options options_handle,
491
+ ?metadata_config metadata_config
492
+ ) -> [String, extended_metadata]
493
+ def convert_with_tables: (
494
+ String html,
495
+ ?conversion_options options,
496
+ ?metadata_config metadata_config
497
+ ) -> table_extraction_result
498
+ end
data/sig/open3.rbs ADDED
@@ -0,0 +1,12 @@
1
+ # Type signature for Open3 standard library
2
+ module Open3
3
+ # Execute command and capture stdout, stderr, and status
4
+ #
5
+ # @param cmd Command to execute
6
+ # @param args Command arguments
7
+ # @return Array containing stdout (String), stderr (String), and status (Process::Status)
8
+ def self.capture3: (
9
+ String cmd,
10
+ *String args
11
+ ) -> [String, String, Process::Status]
12
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'html_to_markdown/cli_proxy'
5
+ require 'html_to_markdown/cli'
6
+ require 'stringio'
7
+
8
+ RSpec.describe HtmlToMarkdown::CLIProxy do
9
+ describe '.call' do
10
+ it 'executes the CLI binary' do
11
+ begin
12
+ binary = described_class.find_cli_binary
13
+ rescue HtmlToMarkdown::CLIProxy::MissingBinaryError
14
+ skip 'CLI binary not built'
15
+ end
16
+
17
+ expect(binary).to be_file
18
+
19
+ output = described_class.call(['--version'])
20
+ expect(output).to include(HtmlToMarkdown::VERSION)
21
+ end
22
+ end
23
+
24
+ describe HtmlToMarkdown::CLI do
25
+ it 'writes CLI output to stdout' do
26
+ begin
27
+ HtmlToMarkdown::CLIProxy.find_cli_binary
28
+ rescue HtmlToMarkdown::CLIProxy::MissingBinaryError
29
+ skip 'CLI binary not built'
30
+ end
31
+
32
+ stdout = StringIO.new
33
+ stderr = StringIO.new
34
+
35
+ exit_code = described_class.run(['--version'], stdout: stdout, stderr: stderr)
36
+
37
+ expect(exit_code).to eq(0)
38
+ expect(stdout.string).to include(HtmlToMarkdown::VERSION)
39
+ expect(stderr.string).to be_empty
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe HtmlToMarkdown do
6
+ describe '.convert' do
7
+ it 'converts simple headings' do
8
+ expect(described_class.convert('<h1>Hello</h1>')).to eq("# Hello\n")
9
+ end
10
+
11
+ it 'accepts options hash' do
12
+ result = described_class.convert(
13
+ '<h1>Hello</h1>',
14
+ heading_style: :atx_closed,
15
+ default_title: true
16
+ )
17
+ expect(result).to include('Hello')
18
+ end
19
+ end
20
+
21
+ describe '.convert_with_inline_images' do
22
+ it 'returns inline images metadata' do
23
+ html = '<p><img src="data:image/png;base64,ZmFrZQ==" alt="fake"></p>'
24
+ extraction = described_class.convert_with_inline_images(html)
25
+ expect(extraction).to include(:markdown, :inline_images, :warnings)
26
+ expect(extraction[:inline_images].first[:description]).to eq('fake')
27
+ end
28
+ end
29
+
30
+ describe '.options' do
31
+ it 'returns a reusable options handle' do
32
+ handle = described_class.options(heading_style: :atx_closed)
33
+ expect(handle).to be_a(HtmlToMarkdown::Options)
34
+ result = described_class.convert_with_options('<h1>Hello</h1>', handle)
35
+ expect(result).to include('# Hello #')
36
+ end
37
+ end
38
+
39
+ describe 'panic handling' do
40
+ context 'when a Rust panic would occur' do
41
+ it 'catches panics in convert method' do
42
+ malformed_html = "#{'<' * 100_000}div#{'>' * 100_000}"
43
+
44
+ begin
45
+ result = described_class.convert(malformed_html)
46
+ expect(result).to be_a(String)
47
+ rescue RuntimeError => e
48
+ expect(e.message).to match(/html-to-markdown panic during conversion/)
49
+ end
50
+ end
51
+
52
+ it 'catches panics in convert_with_options method' do
53
+ malformed_html = "#{'<' * 100_000}div#{'>' * 100_000}"
54
+ handle = described_class.options(heading_style: :atx)
55
+
56
+ begin
57
+ result = described_class.convert_with_options(malformed_html, handle)
58
+ expect(result).to be_a(String)
59
+ rescue RuntimeError => e
60
+ expect(e.message).to match(/html-to-markdown panic during conversion/)
61
+ end
62
+ end
63
+
64
+ it 'catches panics in convert_with_inline_images method' do
65
+ malformed_html = "#{'<' * 100_000}div#{'>' * 100_000}"
66
+
67
+ begin
68
+ result = described_class.convert_with_inline_images(malformed_html)
69
+ expect(result).to be_a(Hash)
70
+ expect(result).to include(:markdown, :inline_images, :warnings)
71
+ rescue RuntimeError => e
72
+ expect(e.message).to match(/html-to-markdown panic during conversion/)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end