commonmarker 0.20.2 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of commonmarker might be problematic. Click here for more details.

@@ -21,6 +21,7 @@ typedef struct {
21
21
  cmark_delim_type delimiter;
22
22
  unsigned char bullet_char;
23
23
  bool tight;
24
+ bool checked; // For task list extension
24
25
  } cmark_list;
25
26
 
26
27
  typedef struct {
@@ -16,6 +16,7 @@ cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
16
16
 
17
17
  typedef struct {
18
18
  uint16_t n_columns;
19
+ int paragraph_offset;
19
20
  cmark_llist *cells;
20
21
  } table_row;
21
22
 
@@ -115,6 +116,7 @@ static table_row *row_from_string(cmark_syntax_extension *self,
115
116
  int len) {
116
117
  table_row *row = NULL;
117
118
  bufsize_t cell_matched = 1, pipe_matched = 1, offset;
119
+ int cell_end_offset;
118
120
 
119
121
  row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
120
122
  row->n_columns = 0;
@@ -129,20 +131,32 @@ static table_row *row_from_string(cmark_syntax_extension *self,
129
131
  pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
130
132
 
131
133
  if (cell_matched || pipe_matched) {
132
- cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
133
- cell_matched);
134
- cmark_strbuf_trim(cell_buf);
135
-
136
- node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
137
- cell->buf = cell_buf;
138
- cell->start_offset = offset;
139
- cell->end_offset = offset + cell_matched - 1;
140
- while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
141
- --cell->start_offset;
142
- ++cell->internal_offset;
134
+ cell_end_offset = offset + cell_matched - 1;
135
+
136
+ if (string[cell_end_offset] == '\n' || string[cell_end_offset] == '\r') {
137
+ row->paragraph_offset = cell_end_offset;
138
+
139
+ cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell);
140
+ row->cells = NULL;
141
+ row->n_columns = 0;
142
+ } else {
143
+ cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
144
+ cell_matched);
145
+ cmark_strbuf_trim(cell_buf);
146
+
147
+ node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
148
+ cell->buf = cell_buf;
149
+ cell->start_offset = offset;
150
+ cell->end_offset = cell_end_offset;
151
+
152
+ while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
153
+ --cell->start_offset;
154
+ ++cell->internal_offset;
155
+ }
156
+
157
+ row->n_columns += 1;
158
+ row->cells = cmark_llist_append(parser->mem, row->cells, cell);
143
159
  }
144
- row->n_columns += 1;
145
- row->cells = cmark_llist_append(parser->mem, row->cells, cell);
146
160
  }
147
161
 
148
162
  offset += cell_matched + pipe_matched;
@@ -161,6 +175,26 @@ static table_row *row_from_string(cmark_syntax_extension *self,
161
175
  return row;
162
176
  }
163
177
 
178
+ static void try_inserting_table_header_paragraph(cmark_parser *parser,
179
+ cmark_node *parent_container,
180
+ unsigned char *parent_string,
181
+ int paragraph_offset) {
182
+ cmark_node *paragraph;
183
+ cmark_strbuf *paragraph_content;
184
+
185
+ paragraph = cmark_node_new_with_mem(CMARK_NODE_PARAGRAPH, parser->mem);
186
+
187
+ paragraph_content = unescape_pipes(parser->mem, parent_string, paragraph_offset);
188
+ cmark_strbuf_trim(paragraph_content);
189
+ cmark_node_set_string_content(paragraph, (char *) paragraph_content->ptr);
190
+ cmark_strbuf_free(paragraph_content);
191
+ parser->mem->free(paragraph_content);
192
+
193
+ if (!cmark_node_insert_before(parent_container, paragraph)) {
194
+ parser->mem->free(paragraph);
195
+ }
196
+ }
197
+
164
198
  static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
165
199
  cmark_parser *parser,
166
200
  cmark_node *parent_container,
@@ -217,6 +251,11 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
217
251
  return parent_container;
218
252
  }
219
253
 
254
+ if (header_row->paragraph_offset) {
255
+ try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string,
256
+ header_row->paragraph_offset);
257
+ }
258
+
220
259
  cmark_node_set_syntax_extension(parent_container, self);
221
260
 
222
261
  parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
@@ -9,19 +9,33 @@ typedef enum {
9
9
  CMARK_TASKLIST_CHECKED,
10
10
  } cmark_tasklist_type;
11
11
 
12
+ // Local constants
13
+ static const char *TYPE_STRING = "tasklist";
14
+
12
15
  static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) {
13
- return "tasklist";
16
+ return TYPE_STRING;
14
17
  }
15
18
 
16
- char *cmark_gfm_extensions_get_tasklist_state(cmark_node *node) {
17
- if (!node || ((int)node->as.opaque != CMARK_TASKLIST_CHECKED && (int)node->as.opaque != CMARK_TASKLIST_NOCHECKED))
19
+
20
+ // Return 1 if state was set, 0 otherwise
21
+ int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked) {
22
+ // The node has to exist, and be an extension, and actually be the right type in order to get the value.
23
+ if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
18
24
  return 0;
19
25
 
20
- if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
21
- return "checked";
26
+ node->as.list.checked = is_checked;
27
+ return 1;
28
+ }
29
+
30
+ bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) {
31
+ if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
32
+ return false;
33
+
34
+ if (node->as.list.checked) {
35
+ return true;
22
36
  }
23
37
  else {
24
- return "unchecked";
38
+ return false;
25
39
  }
26
40
  }
27
41
 
@@ -75,11 +89,7 @@ static cmark_node *open_tasklist_item(cmark_syntax_extension *self,
75
89
  cmark_parser_advance_offset(parser, (char *)input, 3, false);
76
90
 
77
91
  // Either an upper or lower case X means the task is completed.
78
- if (strstr((char*)input, "[x]") || strstr((char*)input, "[X]")) {
79
- parent_container->as.opaque = (void *)CMARK_TASKLIST_CHECKED;
80
- } else {
81
- parent_container->as.opaque = (void *)CMARK_TASKLIST_NOCHECKED;
82
- }
92
+ parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]"));
83
93
 
84
94
  return NULL;
85
95
  }
@@ -90,7 +100,7 @@ static void commonmark_render(cmark_syntax_extension *extension,
90
100
  bool entering = (ev_type == CMARK_EVENT_ENTER);
91
101
  if (entering) {
92
102
  renderer->cr(renderer);
93
- if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
103
+ if (node->as.list.checked) {
94
104
  renderer->out(renderer, node, "- [x] ", false, LITERAL);
95
105
  } else {
96
106
  renderer->out(renderer, node, "- [ ] ", false, LITERAL);
@@ -111,7 +121,7 @@ static void html_render(cmark_syntax_extension *extension,
111
121
  cmark_strbuf_puts(renderer->html, "<li");
112
122
  cmark_html_render_sourcepos(node, renderer->html, options);
113
123
  cmark_strbuf_putc(renderer->html, '>');
114
- if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
124
+ if (node->as.list.checked) {
115
125
  cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" checked=\"\" disabled=\"\" /> ");
116
126
  } else {
117
127
  cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" disabled=\"\" /> ");
@@ -121,6 +131,15 @@ static void html_render(cmark_syntax_extension *extension,
121
131
  }
122
132
  }
123
133
 
134
+ static const char *xml_attr(cmark_syntax_extension *extension,
135
+ cmark_node *node) {
136
+ if (node->as.list.checked) {
137
+ return " completed=\"true\"";
138
+ } else {
139
+ return " completed=\"false\"";
140
+ }
141
+ }
142
+
124
143
  cmark_syntax_extension *create_tasklist_extension(void) {
125
144
  cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist");
126
145
 
@@ -131,6 +150,7 @@ cmark_syntax_extension *create_tasklist_extension(void) {
131
150
  cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
132
151
  cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
133
152
  cmark_syntax_extension_set_html_render_func(ext, html_render);
153
+ cmark_syntax_extension_set_xml_attr_func(ext, xml_attr);
134
154
 
135
155
  return ext;
136
156
  }
@@ -10,8 +10,7 @@ require 'commonmarker/version'
10
10
 
11
11
  begin
12
12
  require 'awesome_print'
13
- rescue LoadError; end
14
-
13
+ rescue LoadError; end # rubocop:disable Lint/SuppressedException
15
14
  module CommonMarker
16
15
  # Public: Parses a Markdown string into an HTML string.
17
16
  #
@@ -21,7 +20,8 @@ module CommonMarker
21
20
  #
22
21
  # Returns a {String} of converted HTML.
23
22
  def self.render_html(text, options = :DEFAULT, extensions = [])
24
- fail TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
23
+ raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
24
+
25
25
  opts = Config.process_options(options, :render)
26
26
  text = text.encode('UTF-8')
27
27
  html = Node.markdown_to_html(text, opts, extensions)
@@ -36,7 +36,8 @@ module CommonMarker
36
36
  #
37
37
  # Returns the `document` node.
38
38
  def self.render_doc(text, options = :DEFAULT, extensions = [])
39
- fail TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
39
+ raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
40
+
40
41
  opts = Config.process_options(options, :parse)
41
42
  text = text.encode('UTF-8')
42
43
  Node.parse_document(text, text.bytesize, opts, extensions)
@@ -37,16 +37,17 @@ module CommonMarker
37
37
  elsif option.is_a?(Array)
38
38
  option = [nil] if option.empty?
39
39
  # neckbearding around. the map will both check the opts and then bitwise-OR it
40
- option.map { |o| check_option(o, type); type.to_h[o] }.inject(0, :|)
40
+ option.map do |o|
41
+ check_option(o, type)
42
+ type.to_h[o]
43
+ end.inject(0, :|)
41
44
  else
42
45
  raise TypeError, "option type must be a valid symbol or array of symbols within the #{type} context"
43
46
  end
44
47
  end
45
48
 
46
49
  def self.check_option(option, type)
47
- unless type.key?(option)
48
- raise TypeError, "option ':#{option}' does not exist for #{type}"
49
- end
50
+ raise TypeError, "option ':#{option}' does not exist for #{type}" unless type.key?(option)
50
51
  end
51
52
  end
52
53
  end
@@ -53,7 +53,7 @@ module CommonMarker
53
53
  end
54
54
 
55
55
  # Public: Iterate over the children (if any) of the current pointer.
56
- def each(&block)
56
+ def each
57
57
  return enum_for(:each) unless block_given?
58
58
 
59
59
  child = first_child
@@ -8,13 +8,13 @@ module CommonMarker
8
8
  PP_INDENT_SIZE = 2
9
9
 
10
10
  def inspect
11
- PP.pp(self, String.new, Float::INFINITY)
11
+ PP.pp(self, +'', Float::INFINITY)
12
12
  end
13
13
 
14
14
  # @param [PrettyPrint] pp
15
- def pretty_print(pp)
16
- pp.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", '>') do
17
- pp.breakable
15
+ def pretty_print(printer)
16
+ printer.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", '>') do
17
+ printer.breakable
18
18
 
19
19
  attrs = %i[
20
20
  sourcepos
@@ -34,22 +34,22 @@ module CommonMarker
34
34
  end
35
35
  end.compact
36
36
 
37
- pp.seplist(attrs) do |name, value|
38
- pp.text "#{name}="
39
- pp.pp value
37
+ printer.seplist(attrs) do |name, value|
38
+ printer.text "#{name}="
39
+ printer.pp value
40
40
  end
41
41
 
42
42
  if first_child
43
- pp.breakable
44
- pp.group(PP_INDENT_SIZE) do
43
+ printer.breakable
44
+ printer.group(PP_INDENT_SIZE) do
45
45
  children = []
46
46
  node = first_child
47
47
  while node
48
48
  children << node
49
49
  node = node.next
50
50
  end
51
- pp.text 'children='
52
- pp.pp children
51
+ printer.text 'children='
52
+ printer.pp children
53
53
  end
54
54
  end
55
55
  end
@@ -8,7 +8,7 @@ module CommonMarker
8
8
  attr_accessor :in_tight, :warnings, :in_plain
9
9
  def initialize(options: :DEFAULT, extensions: [])
10
10
  @opts = Config.process_options(options, :render)
11
- @stream = StringIO.new(''.dup.force_encoding('utf-8'))
11
+ @stream = StringIO.new(+'')
12
12
  @need_blocksep = false
13
13
  @warnings = Set.new []
14
14
  @in_tight = false
@@ -34,7 +34,7 @@ module CommonMarker
34
34
  @node = node
35
35
  if node.type == :document
36
36
  document(node)
37
- return @stream.string
37
+ @stream.string
38
38
  elsif @in_plain && node.type != :text && node.type != :softbreak
39
39
  node.each { |child| render(child) }
40
40
  else
@@ -55,11 +55,11 @@ module CommonMarker
55
55
  code_block(node)
56
56
  end
57
57
 
58
- def reference_def(_node)
59
- end
58
+ def reference_def(_node); end
60
59
 
61
60
  def cr
62
61
  return if @stream.string.empty? || @stream.string[-1] == "\n"
62
+
63
63
  out("\n")
64
64
  end
65
65
 
@@ -111,7 +111,8 @@ module CommonMarker
111
111
  )
112
112
  (?=\s|>|/>)
113
113
  }xi,
114
- '&lt;\1')
114
+ '&lt;\1'
115
+ )
115
116
  else
116
117
  str
117
118
  end
@@ -119,6 +120,7 @@ module CommonMarker
119
120
 
120
121
  def sourcepos(node)
121
122
  return '' unless option_enabled?(:SOURCEPOS)
123
+
122
124
  s = node.sourcepos
123
125
  " data-sourcepos=\"#{s[:start_line]}:#{s[:start_column]}-" \
124
126
  "#{s[:end_line]}:#{s[:end_column]}\""
@@ -2,15 +2,9 @@
2
2
 
3
3
  module CommonMarker
4
4
  class HtmlRenderer < Renderer
5
- def render(node)
6
- super(node)
7
- end
8
-
9
5
  def document(_)
10
6
  super
11
- if @written_footnote_ix
12
- out("</ol>\n</section>\n")
13
- end
7
+ out("</ol>\n</section>\n") if @written_footnote_ix
14
8
  end
15
9
 
16
10
  def header(node)
@@ -71,12 +65,13 @@ module CommonMarker
71
65
 
72
66
  def tasklist(node)
73
67
  return '' unless tasklist?(node)
68
+
74
69
  state = if checked?(node)
75
- 'checked="" disabled=""'
76
- else
77
- 'disabled=""'
70
+ 'checked="" disabled=""'
71
+ else
72
+ 'disabled=""'
78
73
  end
79
- return "><input type=\"checkbox\" #{state} /"
74
+ "><input type=\"checkbox\" #{state} /"
80
75
  end
81
76
 
82
77
  def blockquote(node)
@@ -97,9 +92,7 @@ module CommonMarker
97
92
  block do
98
93
  if option_enabled?(:GITHUB_PRE_LANG)
99
94
  out("<pre#{sourcepos(node)}")
100
- if node.fence_info && !node.fence_info.empty?
101
- out(' lang="', node.fence_info.split(/\s+/)[0], '"')
102
- end
95
+ out(' lang="', node.fence_info.split(/\s+/)[0], '"') if node.fence_info && !node.fence_info.empty?
103
96
  out('><code>')
104
97
  else
105
98
  out("<pre#{sourcepos(node)}><code")
@@ -142,9 +135,7 @@ module CommonMarker
142
135
 
143
136
  def link(node)
144
137
  out('<a href="', node.url.nil? ? '' : escape_href(node.url), '"')
145
- if node.title && !node.title.empty?
146
- out(' title="', escape_html(node.title), '"')
147
- end
138
+ out(' title="', escape_html(node.title), '"') if node.title && !node.title.empty?
148
139
  out('>', :children, '</a>')
149
140
  end
150
141
 
@@ -153,9 +144,7 @@ module CommonMarker
153
144
  plain do
154
145
  out(' alt="', :children, '"')
155
146
  end
156
- if node.title && !node.title.empty?
157
- out(' title="', escape_html(node.title), '"')
158
- end
147
+ out(' title="', escape_html(node.title), '"') if node.title && !node.title.empty?
159
148
  out(' />')
160
149
  end
161
150
 
@@ -169,7 +158,7 @@ module CommonMarker
169
158
  out('</code>')
170
159
  end
171
160
 
172
- def linebreak(node)
161
+ def linebreak(_node)
173
162
  out("<br />\n")
174
163
  end
175
164
 
@@ -210,9 +199,9 @@ module CommonMarker
210
199
 
211
200
  def table_cell(node)
212
201
  align = case @alignments[@column_index]
213
- when :left; ' align="left"'
214
- when :right; ' align="right"'
215
- when :center; ' align="center"'
202
+ when :left then ' align="left"'
203
+ when :right then ' align="right"'
204
+ when :center then ' align="center"'
216
205
  else; ''
217
206
  end
218
207
  out(@in_header ? "<th#{align}#{sourcepos(node)}>" : "<td#{align}#{sourcepos(node)}>", :children, @in_header ? "</th>\n" : "</td>\n")
@@ -228,28 +217,27 @@ module CommonMarker
228
217
  end
229
218
 
230
219
  def footnote_definition(_)
231
- if !@footnote_ix
220
+ unless @footnote_ix
232
221
  out("<section class=\"footnotes\">\n<ol>\n")
233
222
  @footnote_ix = 0
234
223
  end
235
224
 
236
225
  @footnote_ix += 1
237
- out("<li id=\"fn#@footnote_ix\">\n", :children)
238
- if out_footnote_backref
239
- out("\n")
240
- end
226
+ out("<li id=\"fn#{@footnote_ix}\">\n", :children)
227
+ out("\n") if out_footnote_backref
241
228
  out("</li>\n")
242
- #</ol>
243
- #</section>
229
+ # </ol>
230
+ # </section>
244
231
  end
245
232
 
246
233
  private
247
234
 
248
235
  def out_footnote_backref
249
236
  return false if @written_footnote_ix == @footnote_ix
237
+
250
238
  @written_footnote_ix = @footnote_ix
251
239
 
252
- out("<a href=\"#fnref#@footnote_ix\" class=\"footnote-backref\">↩</a>")
240
+ out("<a href=\"#fnref#{@footnote_ix}\" class=\"footnote-backref\">↩</a>")
253
241
  true
254
242
  end
255
243
 
@@ -258,8 +246,7 @@ module CommonMarker
258
246
  end
259
247
 
260
248
  def checked?(node)
261
- node.tasklist_state == 'checked'
249
+ node.tasklist_item_checked?
262
250
  end
263
-
264
251
  end
265
252
  end