md4c 0.0.1 → 0.1.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: c064e8f934590bb55c5edfd5d1cb73821ef8c424347a58d48f9b4c00ccb85b8b
4
- data.tar.gz: 35ca53caca0fd1184ee86a5330b720f00e87af94cfa8fd0f86b1ffaab5ed4819
3
+ metadata.gz: e5fd2f4d2ffb435e1c54d1113f52900e4420d081f07cb9b15ee9edf5ba29e044
4
+ data.tar.gz: 56dddc4ad5106e31c46aef15f6c061d8da99371e38b4be164efcd26f2ae8bb2e
5
5
  SHA512:
6
- metadata.gz: 919321d66c7e6a59676c471d97210c805e60492fabc706ac1b9d53d0d31362edc85d9c8237ea2eee3b67d69fa18f62d12ead2fd30c46ffbacd1222322d732a4b
7
- data.tar.gz: ff3fd7f61e2fcbb8ce5a19d7a67605752df59c67efaa19b2fe7b36f0e2cc45434cd032c5af10f439832abbfb00739bb9fb7b0b216f04c5d36c3839211c5c659c
6
+ metadata.gz: e21e520b23859198597e3a5a15806abac7f65a6df15e37cfe555dafb7462ff9bf89f62e29f8064367df90d14390c2b9b9f11f7a037d377465db8ff293634c26b
7
+ data.tar.gz: 68a9668c3180204220ff663b0ffcd14d3cc92befdf1d02da26908e3204474470cd787250995cd35933afd01e5b7e1f79ae8591c9e40bc1580e0fb689ff7fbddf
data/.envrc CHANGED
@@ -6,3 +6,4 @@ path_add C_INCLUDE_PATH /usr/local/include/ruby-4.0
6
6
  path_add C_INCLUDE_PATH /usr/local/include/ruby-4.0/x86_64-openbsd
7
7
  path_add C_INCLUDE_PATH /usr/local/llvm21/lib/clang/21/include
8
8
  path_add LD_LIBRARY_PATH /usr/local/lib
9
+ export LANG=en_US.UTF-8
data/.rdoc_options ADDED
@@ -0,0 +1,9 @@
1
+ title: MD4C RubyGem
2
+ main_page: README.md
3
+ exclude:
4
+ - Gemfile*
5
+ - Rakefile
6
+ - bin/
7
+ - compile_flags.txt
8
+ - tmp/
9
+ - .direnv/
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.0 - 2026-08-17
6
+
7
+ - Fixed reader methods which gets a sequence of text nodes.
8
+ Remove the `StringAttribute` class and its usages. Now `content`
9
+ methods return `Array` of text nodes.
10
+ - Use UTF-8 encoding for potentially multibyte text contents.
11
+ - Add RBS type definitions.
12
+ - Add some doc-comments.
13
+ - Update readme with simple usages.
14
+
5
15
  ## 0.0.1 - 2026-08-14
6
16
 
7
17
  - Initial release
data/README.md CHANGED
@@ -1,26 +1,58 @@
1
1
  # Markdown for C, for Ruby
2
2
 
3
- This is a [MD4C](https://github.com/mity/md4c) binding for Ruby.
3
+ This is a [MD4C](https://github.com/mity/md4c) binding for Ruby. It
4
+ can parse Markdown documents based on the CommonMark and with some
5
+ extensions, including the GitHub Flavored Markdown. It can also
6
+ convert Markdown documents into HTML.
7
+
8
+ Project links: [Codeberg][repo], [RubyGems.org][rubygems]
9
+
10
+ [rubygems]: https://rubygems.org/gems/md4c
11
+ [repo]: https://codeberg.org/gemmaro/ruby-md4c
4
12
 
5
13
  ## Installation
6
14
 
7
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
15
+ This gem requires the MD4C C library.
8
16
 
9
17
  Install the gem and add to the application's Gemfile by executing:
10
18
 
11
19
  ```bash
12
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
20
+ bundle add md4c
13
21
  ```
14
22
 
15
23
  If bundler is not being used to manage dependencies, install the gem by executing:
16
24
 
17
25
  ```bash
18
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
26
+ gem install md4c
19
27
  ```
20
28
 
21
29
  ## Usage
22
30
 
23
- TODO: Write usage instructions here
31
+ The parser uses the callback approach:
32
+
33
+ ```ruby
34
+ require 'md4c'
35
+ parser = MD4C::Parser.new
36
+
37
+ parser.on_enter_block do |block|
38
+ # do something with block
39
+ end
40
+
41
+ parser.parse(source)
42
+ ```
43
+
44
+ There are also `on_leave_block`, `on_enter_span`, `on_leave_span`, and
45
+ `on_text` methods.
46
+
47
+ The `Parser`'s `initialize` method takes options for extensions. For example,
48
+
49
+ ```ruby
50
+ parser = MD4C::Parser.new(permissive_atx_headers: true)
51
+ # [...]
52
+ parser.parse(<<~END_MARKDOWN)
53
+ #this is now header
54
+ END_MARKDOWN
55
+ ```
24
56
 
25
57
  ## Development
26
58
 
@@ -37,7 +69,7 @@ tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
69
 
38
70
  ## Contributing
39
71
 
40
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/md4c.
72
+ Bug reports and pull requests are welcome.
41
73
 
42
74
  ## License
43
75
 
data/ext/md4c/rb_md4c.c CHANGED
@@ -21,9 +21,10 @@
21
21
  #include <ruby.h>
22
22
  #include <stddef.h>
23
23
 
24
- /* rbm_ prefix means "Ruby MD4C". */
24
+ /* rbm_ prefix means "Ruby MD4C".
25
+ */
25
26
 
26
- static VALUE rb_mMD4C, rbm_cParser, rbm_cStringAttribute, /* blocks --> */
27
+ static VALUE rb_mMD4C, rbm_cParser, /* blocks --> */
27
28
  rbm_cUnorderedList, rbm_cOrderedList, rbm_cListItem, rbm_cHeading,
28
29
  rbm_cCodeBlock, rbm_cTable, rbm_cTableHeader, rbm_cTableData,
29
30
  /* spans --> */ rbm_cAnchor, rbm_cImage, rbm_cWikiLink,
@@ -157,6 +158,11 @@ rbm_get_parser_flags (ID kwargs_vals[])
157
158
  return flags;
158
159
  }
159
160
 
161
+ /**
162
+ * :call-seq: Parser.new (**flags) -> Parser
163
+ *
164
+ * All flags are false by default, which means it is CommonMark.
165
+ */
160
166
  static VALUE
161
167
  rbm_cParser_m_initialize (int argc, VALUE *argv, VALUE self)
162
168
  {
@@ -172,6 +178,9 @@ rbm_cParser_m_initialize (int argc, VALUE *argv, VALUE self)
172
178
  return self;
173
179
  }
174
180
 
181
+ /**
182
+ * :call-seq: on_enter_block { |block_node| ... }
183
+ */
175
184
  static VALUE
176
185
  rbm_cParser_m_on_enter_block (VALUE self)
177
186
  {
@@ -179,6 +188,9 @@ rbm_cParser_m_on_enter_block (VALUE self)
179
188
  return p->on_enter_block = rb_block_proc ();
180
189
  }
181
190
 
191
+ /**
192
+ * :call-seq: on_leave_block { |block_node| ... }
193
+ */
182
194
  static VALUE
183
195
  rbm_cParser_m_on_leave_block (VALUE self)
184
196
  {
@@ -186,6 +198,9 @@ rbm_cParser_m_on_leave_block (VALUE self)
186
198
  return p->on_leave_block = rb_block_proc ();
187
199
  }
188
200
 
201
+ /**
202
+ * :call-seq: on_enter_span { |span_node| ... }
203
+ */
189
204
  static VALUE
190
205
  rbm_cParser_m_on_enter_span (VALUE self)
191
206
  {
@@ -193,6 +208,9 @@ rbm_cParser_m_on_enter_span (VALUE self)
193
208
  return p->on_enter_span = rb_block_proc ();
194
209
  }
195
210
 
211
+ /**
212
+ * :call-seq: on_leave_span { |span_node| ... }
213
+ */
196
214
  static VALUE
197
215
  rbm_cParser_m_on_leave_span (VALUE self)
198
216
  {
@@ -200,6 +218,9 @@ rbm_cParser_m_on_leave_span (VALUE self)
200
218
  return p->on_leave_span = rb_block_proc ();
201
219
  }
202
220
 
221
+ /**
222
+ * :call-seq: on_text { |text_node| ... }
223
+ */
203
224
  static VALUE
204
225
  rbm_cParser_m_on_text (VALUE self)
205
226
  {
@@ -207,26 +228,6 @@ rbm_cParser_m_on_text (VALUE self)
207
228
  return p->on_text = rb_block_proc ();
208
229
  }
209
230
 
210
- static size_t
211
- rbm_cStringAttribute_size (const void *ptr)
212
- {
213
- return sizeof (MD_ATTRIBUTE);
214
- }
215
-
216
- static const rb_data_type_t rbm_cStringAttribute_type
217
- = { .wrap_struct_name = "MD4C string attribute",
218
- .function
219
- = { .dfree = RUBY_DEFAULT_FREE, .dsize = rbm_cStringAttribute_size },
220
- .flags = RUBY_TYPED_FREE_IMMEDIATELY };
221
-
222
- static VALUE
223
- rbm_cStringAttribute_alloc (VALUE self)
224
- {
225
- MD_BLOCK_CODE_DETAIL *attr = ruby_xmalloc (sizeof (MD_ATTRIBUTE));
226
- return rb_data_typed_object_wrap (rbm_cStringAttribute, attr,
227
- &rbm_cStringAttribute_type);
228
- }
229
-
230
231
  static VALUE
231
232
  rbm_get_text_class (MD_TEXTTYPE type)
232
233
  {
@@ -259,23 +260,22 @@ rbm_get_text_class (MD_TEXTTYPE type)
259
260
  }
260
261
 
261
262
  static VALUE
262
- rbm_cStringAttribute_m_each (VALUE self)
263
+ rbm_from_string_attribute (MD_ATTRIBUTE attr)
263
264
  {
264
- RETURN_ENUMERATOR (self, 0, NULL);
265
-
266
- MD_ATTRIBUTE *attr = RTYPEDDATA_DATA (self);
267
- size_t index = 0;
268
- while (attr->substr_offsets[index] >= attr->size)
265
+ if (attr.text == NULL)
266
+ return RUBY_Qnil;
267
+ VALUE ary = rb_ary_new ();
268
+ for (size_t index = 0; attr.substr_offsets[index] < attr.size; index++)
269
269
  {
270
- VALUE klass = rbm_get_text_class (attr->substr_types[index]);
271
- VALUE substr = rb_class_new_instance (0, NULL, klass);
272
- VALUE content = rb_str_new (attr->text + attr->substr_offsets[index],
273
- attr->substr_offsets[index + 1]
274
- - attr->substr_offsets[index]);
275
- rb_iv_set (substr, "@content", content);
276
- rb_yield (substr);
270
+ VALUE klass = rbm_get_text_class (attr.substr_types[index]);
271
+ VALUE text = rb_class_new_instance (0, NULL, klass);
272
+ VALUE content = rb_utf8_str_new (attr.text + attr.substr_offsets[index],
273
+ attr.substr_offsets[index + 1]
274
+ - attr.substr_offsets[index]);
275
+ rb_iv_set (text, "@content", content);
276
+ rb_ary_push (ary, text);
277
277
  }
278
- return RUBY_Qnil;
278
+ return ary;
279
279
  }
280
280
 
281
281
  static VALUE
@@ -354,12 +354,10 @@ rbm_callback_block_arguments (MD_BLOCKTYPE type, void *detail)
354
354
  arg = rb_class_new_instance (0, NULL, rbm_cCodeBlock);
355
355
  MD_BLOCK_CODE_DETAIL *d = detail;
356
356
 
357
- VALUE info = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
358
- RTYPEDDATA_DATA (info) = &d->info;
357
+ VALUE info = rbm_from_string_attribute (d->info);
359
358
  rb_iv_set (arg, "@info", info);
360
359
 
361
- VALUE lang = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
362
- RTYPEDDATA_DATA (lang) = &d->lang;
360
+ VALUE lang = rbm_from_string_attribute (d->lang);
363
361
  rb_iv_set (arg, "@lang", lang);
364
362
 
365
363
  VALUE fence
@@ -495,12 +493,10 @@ rbm_callback_span_arguments (MD_SPANTYPE type, void *detail)
495
493
 
496
494
  MD_SPAN_A_DETAIL *d = detail;
497
495
 
498
- VALUE href = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
499
- RTYPEDDATA_DATA (href) = &d->href;
496
+ VALUE href = rbm_from_string_attribute (d->href);
500
497
  rb_iv_set (arg, "@href", href);
501
498
 
502
- VALUE title = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
503
- RTYPEDDATA_DATA (title) = &d->title;
499
+ VALUE title = rbm_from_string_attribute (d->title);
504
500
  rb_iv_set (arg, "@title", title);
505
501
 
506
502
  VALUE autolink = d->is_autolink ? RUBY_Qtrue : RUBY_Qfalse;
@@ -514,12 +510,10 @@ rbm_callback_span_arguments (MD_SPANTYPE type, void *detail)
514
510
 
515
511
  MD_SPAN_IMG_DETAIL *d = detail;
516
512
 
517
- VALUE src = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
518
- RTYPEDDATA_DATA (src) = &d->src;
513
+ VALUE src = rbm_from_string_attribute (d->src);
519
514
  rb_iv_set (arg, "@src", src);
520
515
 
521
- VALUE title = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
522
- RTYPEDDATA_DATA (title) = &d->title;
516
+ VALUE title = rbm_from_string_attribute (d->title);
523
517
  rb_iv_set (arg, "@title", title);
524
518
  break;
525
519
  }
@@ -547,8 +541,7 @@ rbm_callback_span_arguments (MD_SPANTYPE type, void *detail)
547
541
 
548
542
  MD_SPAN_WIKILINK_DETAIL *d = detail;
549
543
 
550
- VALUE target = rb_class_new_instance (0, NULL, rbm_cStringAttribute);
551
- RTYPEDDATA_DATA (target) = &d->target;
544
+ VALUE target = rbm_from_string_attribute (d->target);
552
545
  rb_iv_set (arg, "@target", target);
553
546
  break;
554
547
  }
@@ -592,7 +585,7 @@ rbm_callback_text (MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size,
592
585
  struct rbm_cParser *p = userdata;
593
586
  if (!p->on_text)
594
587
  return 0;
595
- VALUE string = rb_str_new (text, size);
588
+ VALUE string = rb_utf8_str_new (text, size);
596
589
  VALUE klass = rbm_get_text_class (type);
597
590
  VALUE args = rb_ary_new ();
598
591
  VALUE arg = rb_class_new_instance (0, NULL, klass);
@@ -602,6 +595,9 @@ rbm_callback_text (MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size,
602
595
  return 0;
603
596
  }
604
597
 
598
+ /**
599
+ * :call-seq: parse (source)
600
+ */
605
601
  static VALUE
606
602
  rbm_cParser_m_parse (VALUE self, VALUE source)
607
603
  {
@@ -632,12 +628,17 @@ rbm_markdown_to_html_callback (const MD_CHAR *input, MD_SIZE size,
632
628
  void *userdata)
633
629
  {
634
630
  struct rbm_markdown_to_html_userdata *data = userdata;
635
- VALUE text = rb_str_new (input, size);
631
+ VALUE text = rb_utf8_str_new (input, size);
636
632
  VALUE args = rb_ary_new ();
637
633
  rb_ary_push (args, text);
638
634
  rb_proc_call (data->block, args);
639
635
  }
640
636
 
637
+ /**
638
+ * call-seq: MD4C.markdown_to_html (input, verbatim_entities: false, skip_utf8_bom: false, xhtml: false, **parser_flags) { |chunk| ... }
639
+ *
640
+ * +parser_flags+ are the same flags as MD4C::Parser.new method's.
641
+ */
641
642
  static VALUE
642
643
  rbm_m_markdown_to_html (int argc, VALUE *argv, VALUE self)
643
644
  {
@@ -686,12 +687,6 @@ Init_md4c (void)
686
687
  {
687
688
  rb_mMD4C = rb_define_module ("MD4C");
688
689
 
689
- rbm_cStringAttribute
690
- = rb_define_class_under (rb_mMD4C, "StringAttribute", rb_cObject);
691
- rb_define_alloc_func (rbm_cStringAttribute, rbm_cStringAttribute_alloc);
692
- rb_define_method (rbm_cStringAttribute, "each", rbm_cStringAttribute_m_each,
693
- 0);
694
-
695
690
  rbm_cUnorderedList
696
691
  = rb_define_class_under (rb_mMD4C, "UnorderedList", rb_cObject);
697
692
  rbm_cOrderedList
data/lib/md4c/version.rb CHANGED
@@ -18,5 +18,5 @@
18
18
  #++
19
19
 
20
20
  module MD4C
21
- VERSION = "0.0.1"
21
+ VERSION = "0.1.0"
22
22
  end
data/lib/md4c.rb CHANGED
@@ -23,11 +23,13 @@ require "md4c/md4c"
23
23
  # The class naming is based off the CommonMark specification, the
24
24
  # GitHub Flavored Markdown specification, and the MDN document about
25
25
  # HTML.
26
+ #
27
+ # Some classes have +content+ reader method (attribute). This returns
28
+ # Array of text nodes.
29
+ #
30
+ # For potentially multibyte contents, UTF-8 encoding is used.
31
+ # Otherwise no encoding is specified (which means US-ASCII).
26
32
  module MD4C
27
- class StringAttribute
28
- include Enumerable
29
- end
30
-
31
33
  class UnorderedList
32
34
  attr_reader :mark
33
35
 
@@ -96,7 +98,7 @@ module MD4C
96
98
  attr_reader :target
97
99
  end
98
100
 
99
- module EqualByContent
101
+ module EqualByContent # :nodoc:
100
102
  def ==(other)
101
103
  self.class == other.class && @content == other.content
102
104
  end
data/sig/md4c.rbs CHANGED
@@ -1,4 +1,128 @@
1
1
  module MD4C
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ type text_node = MD4C::NormalText | MD4C::NullChar | MD4C::HardBreak | MD4C::SoftBreak | MD4C::Entity | MD4C::CodeText | MD4C::HTMLText | MD4C::LaTeXMathText
5
+ type block_node = MD4C::UnorderedList | MD4C::OrderedList | MD4C::ListItem | MD4C::Heading | MD4C::CodeBlock | MD4C::Table | MD4C::TableHeader | MD4C::TableData
6
+ type span_node = MD4C::Anchor | MD4C::Image | MD4C::WikiLink
7
+ type align = nil | :left | :center | :right
8
+
9
+ def self.markdown_to_html: (input: String) { (text_node) -> void } -> void
10
+
11
+ class Anchor
12
+ attr_reader href: Array[text_node]
13
+ attr_reader title: Array[text_node]
14
+ def autolink?: -> bool
15
+ end
16
+
17
+ class CodeBlock
18
+ attr_reader fence_char: String
19
+ attr_reader info: Array[text_node]
20
+ attr_reader lang: Array[text_node]
21
+ end
22
+
23
+ class CodeText
24
+ attr_reader content: Array[text_node]
25
+ end
26
+
27
+ class Entity
28
+ attr_reader content: Array[text_node]
29
+ end
30
+
31
+ class HTMLText
32
+ attr_reader content: Array[text_node]
33
+ end
34
+
35
+ class HardBreak
36
+ attr_reader content: Array[text_node]
37
+ end
38
+
39
+ class Heading
40
+ attr_reader level: Integer
41
+ end
42
+
43
+ class Image
44
+ attr_reader src: Array[text_node]
45
+ attr_reader title: Array[text_node]
46
+ end
47
+
48
+ class LaTeXMathText
49
+ attr_reader content: Array[text_node]
50
+ end
51
+
52
+ class ListItem
53
+ attr_reader task_mark: String
54
+ attr_reader task_mark_offset: Integer
55
+ def task?: -> bool
56
+ end
57
+
58
+ class NormalText
59
+ attr_reader content: Array[text_node]
60
+ end
61
+
62
+ class NullChar
63
+ attr_reader content: Array[text_node]
64
+ end
65
+
66
+ class OrderedList
67
+ attr_reader mark_delimiter: String
68
+ attr_reader start: Integer
69
+ def tight?: -> bool
70
+ end
71
+
72
+ class Parser
73
+ def initialize: (
74
+ collapse_whitespace: bool,
75
+ permissive_atx_headers: bool,
76
+ permissive_url_autolinks: bool,
77
+ permissive_email_autolinks: bool,
78
+ no_indented_codeblocks: bool,
79
+ no_html_blocks: bool,
80
+ no_html_spans: bool,
81
+ tables: bool,
82
+ strikethrough: bool,
83
+ permissive_www_autolinks: bool,
84
+ task_lists: bool,
85
+ latex_math_spans: bool,
86
+ wiki_links: bool,
87
+ underline: bool,
88
+ hard_soft_breaks: bool,
89
+ permissive_autolinks: bool,
90
+ no_html: bool,
91
+ commonmark: bool,
92
+ github: bool) -> MD4C::Parser
93
+
94
+ def on_enter_block: { (block_node) -> void } -> void
95
+ def on_leave_block: { (block_node) -> void } -> void
96
+ def on_enter_span: { (span_node) -> void } -> void
97
+ def on_leave_span: { (span_node) -> void } -> void
98
+ def on_text: { (text_node) -> void } -> void
99
+ def parse: (String) -> void
100
+ end
101
+
102
+ class SoftBreak
103
+ attr_reader content: Array[text_node]
104
+ end
105
+
106
+ class Table
107
+ attr_reader body_row_count: Integer
108
+ attr_reader col_count: Integer
109
+ attr_reader head_row_count: Integer
110
+ end
111
+
112
+ class TableData
113
+ attr_reader align: align
114
+ end
115
+
116
+ class TableHeader
117
+ attr_reader align: align
118
+ end
119
+
120
+ class UnorderedList
121
+ attr_reader mark: String
122
+ def tight?: -> bool
123
+ end
124
+
125
+ class WikiLink
126
+ attr_reader target: Array[text_node]
127
+ end
4
128
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: md4c
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gemmaro
@@ -21,6 +21,7 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - ".clang-format"
23
23
  - ".envrc"
24
+ - ".rdoc_options"
24
25
  - CHANGELOG.md
25
26
  - COPYING
26
27
  - README.md