commonmarker 0.20.1 → 0.22.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
  }
data/lib/commonmarker.rb CHANGED
@@ -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)
@@ -1,52 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby-enum'
4
3
  module CommonMarker
5
4
  # For Ruby::Enum, these must be classes, not modules
6
5
  module Config
7
- class Parse
8
- include Ruby::Enum
9
-
10
- define :DEFAULT, 0
11
- define :VALIDATE_UTF8, (1 << 9)
12
- define :SMART, (1 << 10)
13
- define :LIBERAL_HTML_TAG, (1 << 12)
14
- define :FOOTNOTES, (1 << 13)
15
- define :STRIKETHROUGH_DOUBLE_TILDE, (1 << 14)
16
- define :UNSAFE, (1 << 17)
17
- end
18
-
19
- class Render
20
- include Ruby::Enum
21
-
22
- define :DEFAULT, 0
23
- define :SOURCEPOS, (1 << 1)
24
- define :HARDBREAKS, (1 << 2)
25
- define :NOBREAKS, (1 << 4)
26
- define :GITHUB_PRE_LANG, (1 << 11)
27
- define :TABLE_PREFER_STYLE_ATTRIBUTES, (1 << 15)
28
- define :FULL_INFO_STRING, (1 << 16)
29
- define :UNSAFE, (1 << 17)
30
- end
6
+ # See https://github.com/github/cmark-gfm/blob/master/src/cmark-gfm.h#L673
7
+ OPTS = {
8
+ parse: {
9
+ DEFAULT: 0,
10
+ VALIDATE_UTF8: (1 << 9),
11
+ SMART: (1 << 10),
12
+ LIBERAL_HTML_TAG: (1 << 12),
13
+ FOOTNOTES: (1 << 13),
14
+ STRIKETHROUGH_DOUBLE_TILDE: (1 << 14),
15
+ UNSAFE: (1 << 17)
16
+ }.freeze,
17
+ render: {
18
+ DEFAULT: 0,
19
+ SOURCEPOS: (1 << 1),
20
+ HARDBREAKS: (1 << 2),
21
+ NOBREAKS: (1 << 4),
22
+ GITHUB_PRE_LANG: (1 << 11),
23
+ TABLE_PREFER_STYLE_ATTRIBUTES: (1 << 15),
24
+ FULL_INFO_STRING: (1 << 16),
25
+ UNSAFE: (1 << 17),
26
+ FOOTNOTES: (1 << 13)
27
+ }.freeze
28
+ }.freeze
31
29
 
32
30
  def self.process_options(option, type)
33
- type = Config.const_get(type.capitalize)
34
- if option.is_a?(Symbol)
35
- check_option(option, type)
36
- type.to_h[option]
37
- elsif option.is_a?(Array)
38
- option = [nil] if option.empty?
31
+ case option
32
+ when Symbol
33
+ OPTS.fetch(type).fetch(option)
34
+ when Array
35
+ raise TypeError if option.none?
36
+
39
37
  # 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, :|)
38
+ OPTS.fetch(type).fetch_values(*option).inject(0, :|)
41
39
  else
42
- raise TypeError, "option type must be a valid symbol or array of symbols within the #{type} context"
43
- end
44
- end
45
-
46
- def self.check_option(option, type)
47
- unless type.key?(option)
48
- raise TypeError, "option ':#{option}' does not exist for #{type}"
40
+ raise TypeError, "option type must be a valid symbol or array of symbols within the #{name}::OPTS[:#{type}] context"
49
41
  end
42
+ rescue KeyError => e
43
+ raise TypeError, "option ':#{e.key}' does not exist for #{name}::OPTS[:#{type}]"
50
44
  end
51
45
  end
52
46
  end
@@ -11,7 +11,7 @@ module CommonMarker
11
11
  #
12
12
  # blk - A {Proc} representing the action to take for each child
13
13
  def walk(&block)
14
- return enum_for(:walk) unless block_given?
14
+ return enum_for(:walk) unless block
15
15
 
16
16
  yield self
17
17
  each do |child|
@@ -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
- # @param [PrettyPrint] pp
15
- def pretty_print(pp)
16
- pp.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", '>') do
17
- pp.breakable
14
+ # @param printer [PrettyPrint] pp
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
@@ -27,29 +27,27 @@ module CommonMarker
27
27
  list_tight
28
28
  fence_info
29
29
  ].map do |name|
30
- begin
31
- [name, __send__(name)]
32
- rescue NodeError
33
- nil
34
- end
30
+ [name, __send__(name)]
31
+ rescue NodeError
32
+ nil
35
33
  end.compact
36
34
 
37
- pp.seplist(attrs) do |name, value|
38
- pp.text "#{name}="
39
- pp.pp value
35
+ printer.seplist(attrs) do |name, value|
36
+ printer.text "#{name}="
37
+ printer.pp value
40
38
  end
41
39
 
42
40
  if first_child
43
- pp.breakable
44
- pp.group(PP_INDENT_SIZE) do
41
+ printer.breakable
42
+ printer.group(PP_INDENT_SIZE) do
45
43
  children = []
46
44
  node = first_child
47
45
  while node
48
46
  children << node
49
47
  node = node.next
50
48
  end
51
- pp.text 'children='
52
- pp.pp children
49
+ printer.text 'children='
50
+ printer.pp children
53
51
  end
54
52
  end
55
53
  end
@@ -6,9 +6,10 @@ require 'stringio'
6
6
  module CommonMarker
7
7
  class Renderer
8
8
  attr_accessor :in_tight, :warnings, :in_plain
9
+
9
10
  def initialize(options: :DEFAULT, extensions: [])
10
11
  @opts = Config.process_options(options, :render)
11
- @stream = StringIO.new(''.dup.force_encoding('utf-8'))
12
+ @stream = StringIO.new(+'')
12
13
  @need_blocksep = false
13
14
  @warnings = Set.new []
14
15
  @in_tight = false
@@ -18,11 +19,12 @@ module CommonMarker
18
19
 
19
20
  def out(*args)
20
21
  args.each do |arg|
21
- if arg == :children
22
+ case arg
23
+ when :children
22
24
  @node.each { |child| out(child) }
23
- elsif arg.is_a?(Array)
25
+ when Array
24
26
  arg.each { |x| render(x) }
25
- elsif arg.is_a?(Node)
27
+ when Node
26
28
  render(arg)
27
29
  else
28
30
  @stream.write(arg)
@@ -34,7 +36,7 @@ module CommonMarker
34
36
  @node = node
35
37
  if node.type == :document
36
38
  document(node)
37
- return @stream.string
39
+ @stream.string
38
40
  elsif @in_plain && node.type != :text && node.type != :softbreak
39
41
  node.each { |child| render(child) }
40
42
  else
@@ -55,11 +57,11 @@ module CommonMarker
55
57
  code_block(node)
56
58
  end
57
59
 
58
- def reference_def(_node)
59
- end
60
+ def reference_def(_node); end
60
61
 
61
62
  def cr
62
63
  return if @stream.string.empty? || @stream.string[-1] == "\n"
64
+
63
65
  out("\n")
64
66
  end
65
67
 
@@ -111,7 +113,8 @@ module CommonMarker
111
113
  )
112
114
  (?=\s|>|/>)
113
115
  }xi,
114
- '&lt;\1')
116
+ '&lt;\1'
117
+ )
115
118
  else
116
119
  str
117
120
  end
@@ -119,13 +122,14 @@ module CommonMarker
119
122
 
120
123
  def sourcepos(node)
121
124
  return '' unless option_enabled?(:SOURCEPOS)
125
+
122
126
  s = node.sourcepos
123
127
  " data-sourcepos=\"#{s[:start_line]}:#{s[:start_column]}-" \
124
128
  "#{s[:end_line]}:#{s[:end_column]}\""
125
129
  end
126
130
 
127
131
  def option_enabled?(opt)
128
- (@opts & CommonMarker::Config::Render.value(opt)) != 0
132
+ (@opts & CommonMarker::Config::OPTS.dig(:render, opt)) != 0
129
133
  end
130
134
  end
131
135
  end